知识点:
- 序列化与反序列化
protected修饰的属性被序列化时,会加上\00*\00前缀
private修饰的属性被序列化时,会加上\00类名\00前缀
- php伪协议读取文件内容
php://filter/convert.base64-encode/resource=文件名
打开靶场发现源码
<?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);
}
}
代码审计:
首先是接收get参数str,然后检查是否合法,再将其反序列化
is_valid
检查输入的ascii码要在32~125之间
分析FileHandler
如果op为1,则写内容,op为2读内容,其他则输出bad hacker
__destruct()
若op为2,则将op改为1
--------------------------------------------------------------分析完毕------------------------------------------------------------------------
三个问题
1、读文件
这里使用php伪协议,读哪个文件?由源码开头的include文件包含提示,直接读取flag.php
2、如何绕过destruct中对op的检验,我们想要读取文件,就必须使得op等于2,但反序列化时,必定会调用destruct(原因是destruct是在一个对象被销毁时会被调用,反序列化时字符串将被销毁导致该方法被调用)
绕过:destruct()中是强类型比较,而process()中是弱类型,所以只要将op定义为int型的2就可
3、protected修饰的属性反序列化产生的%00ascii为0,如何绕过?PHP7.1+对类的属性类型不敏感,所以在构造序列化字符串前将修饰改为public即可
构造str
页面输出一串base64编码,解密得到flag