反序列化漏洞分析讲解

tech2026-01-18  3

一、什么是反序列化

1.1 漏洞简介 PHP反序列化漏洞也叫PHP对象注入,是一个非常常见的漏洞,这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果。漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。反序列化漏洞并不是PHP特有,也存在于Java、Python等语言之中,但其原理基本相通。 1.2反序列化函数 在我们讲PHP反序列化的时候,基本都是围绕着serialize(),**unserialize()**这两个函数。那么什么是序列化呢,序列化说通俗点就是把一个对象变成可以传输的字符串。举个例子,不知道大家知不知道json格式,这就是一种序列化,有可能就是通过array序列化而来的。而反序列化就是把那串可以传输的字符串再变回对象。 直接上例子便于理解: 我们先讲一讲比较简单的序列化,我们就用序列化json来举例子吧。虽然序列化Json和我们讲PHP反序列化的漏洞没有什么关系。但是在理解序列化这个概念和之后的内容会有所帮助 json_encode() json_decode() 这两个函数,一眼就能看出来是做什么用的吧,直接上例子:

<?php $book = array('book1'=>'Harry Potter', 'book2'=>'MR.Bean', 'book3'=>'History'); $json = json_encode($book); echo $json; ?>

这边有一个book的数组 ‘book1’=>‘Harry Potter’, ‘book2’=>‘MR.Bean’, ‘Book3’=>‘Python Cookbook’, ‘Book4’=>‘History’ 如果我们想传输这个数组怎么办呢,我们就可以请json_encode()这个函数帮助我们将这个数组序列化成一串字符串 所以在这里,我们将数组序列化成json格式的字串的目的就是为了方便传输。我们可以看见,这里json格式来保存数据主要是使用键值对的形式。 假设,我们写了一个class,这个class里面存有一些变量。当这个class被实例化了之后,在使用过程中里面的一些变量值发生了改变。以后在某些时候还会用到这个变量,如果我们让这个class一直不销毁,等着下一次要用它的时候再一次被调用的话,浪费系统资源。当我们写一个小型的项目可能没有太大的影响,但是随着项目的壮大,一些小问题被放大了之后就会产生很多麻烦。这个时候PHP就和我们说,你可以把这个对象序列化了,存成一个字符串,当你要用的时候再放他出来就好了。

<?php class DemoClass { public $name = 'sms2056'; public $sex = 'man'; public $age = '7'; } $example = new DemoClass(); $example->name = 'jone'; $example->sex = 'woman'; $example->age = '18'; ?>

这里,我们先创了个DemoClass,里面存了点信息,后来我们new了一个实例$example的时候,将这个class里的一些信息给改变了。 如果我们之后还要用到这个实例怎么办呢,我们就先将他序列化存起来,到时候用的时候再放出来就好啦。 是不是很简单,只要用serialize()这个函数就行了

<?php class DemoClass { public $name = 'sms2056'; public $sex = 'man'; public $age = '7'; } $example = new DemoClass(); $example->name = 'jone'; $example->sex = 'woman'; $example->age = '18'; echo serialize($example); ?>

这个时候,我们发现这次序列化出来的格式,和我们上一个序列化json的格式有点不同呢,解释一下: 然后如果反序列化回来的话

<?php class DemoClass { public $name = 'sms2056'; public $sex = 'man'; public $age = '7'; } $example = new DemoClass(); $example->name = 'jone'; $example->sex = 'woman'; $example->age = '18'; $val = serialize($example); $NewExample = unserialize($val); echo $NewExample->age; ?>

二. 为什么会产生漏洞

那么,问题来了,这么序列化一下然后反序列化,为什么就能产生漏洞了呢? 这个时候,我们就要了解一下PHP里面的魔术方法了,魔法函数一般是以__开头,通常会因为某些条件而触发不用我们手动调用: 在研究反序列化漏洞的时候,碰见这几个魔法函数就要仔细研究研究了:

__construct()当一个对象创建时被调用 __destruct()当一个对象销毁时被调用 __toString()当一个对象被当作一个字符串使用 __sleep() 在对象在被序列化之前运行 __wakeup将在序列化之后立即被调用

这些就是我们要关注的几个魔术方法了,如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了。 举个别人的例子:

<?php class A{ var $test = "demo"; function __destruct(){ echo $this->test; } } $a = $_GET['test']; $a_unser = unserialize($a); ?>

这里我们只要构造payload: http://127.0.0.1/test.php?test=O:1:“A”:1:{s:4:“test”;s:5:“hello”;} 就能控制echo出的变量,比如你能拿这个来进行反射型xss。

三、 实例分析

这里实战一题比较简单的ctf题目吧

<?php require_once('shield.php'); $x = new Shield(); isset($_GET['class']) && $g = $_GET['class']; if (!empty($g)) { $x = unserialize($g); } echo $x->readfile(); ?>

可以看见 先是包含了shield.php 然后从中new了个新的实例出来 最后接收用户的反序列化 输出readfile()方法 跟进:

<?php //flag is in pctf.php class Shield { public $file; function __construct($filename = '') { $this -> file = $filename; } function readfile() { if (!empty($this->file) && stripos($this->file,'..')===FALSE && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) { return @file_get_contents($this->file); } } } ?>

这里我们可以看见只要操控$file这个参数为pctf.php就可以了,这里construct函数在实例被创建的时候(也就是new Shield()的时候)执行,所以不会影响我们对$file的操作直接构造序列化对象传过去 O:6:“Shield”:1:{s:4:“file”;s:8:“pctf.php”;} 就行了。

四、反序列漏洞的利用思路

在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化有一种漏洞利用方法叫做 “面向属性编程” ,即 POP( Property Oriented Programming)。和二进制漏洞中常用的ROP技术类似。在ROP中我们往往需要一段初始化gadgets来开始我们的整个利用过程,然后继续调用其他gadgets。在PHP反序列化漏洞利用技术POP中,对应的初始化gadgets就是__wakeup() 或者是__destruct() 方法, 在最理想的情况下能够实现漏洞利用的点就在这两个函数中,但往往我们需要从这个函数开始,逐步的跟进在这个函数中调用到的所有函数,直至找到可以利用的点为止。下面列举些在跟进其函数调用过程中需要关注一些很有价值的函数。 如果在跟进程序过程中发现这些函数就要打起精神,一旦这些函数的参数我们能够控制,就有可能出现高危漏洞.

五、现实中查找反序列化漏洞的方法

PHP的 unserialize() 函数只能反序列化在当前程序上下文中已经被定义过的类.在传统的PHP中你需要通过使用一大串的include() 或者 require()来包含所需的类定义文件。于是后来出现了 autoloading 技术,他可以自动导入需要使用的类,再也不需要程序员不断地复制粘贴 那些include代码了。这种技术同时也方便了我们的漏洞利用.因为在我们找到一个反序列化点的时候我们所能使用的类就多了,那么实现漏洞利用的可能性也就更加高。 还有一个东西要提一下,那就是Composer,这是一个php的包管理工具,同时他还能自动导入所以依赖库中定义的类。这样一来 unserialize() 函数也就能使用所有依赖库中的类了,攻击面又增大不少。 1.Composer配置的依赖库存储在vendor目录下 2.如果要使用Composer的自动类加载机制,只需要在php文件的开头加上 require __DIR__ . '/vendor/autoload.php'; 找PHP链的基本思路. 1.在各大流行的包中搜索 __wakeup() 和 __destruct() 函数. 2.追踪调用过程 3.手工构造 并验证 POP 链 4.开发一个应用使用该库和自动加载机制,来测试exploit. 构造exploit的思路 1.寻找可能存在漏洞的应用 2.在他所使用的库中寻找 POP gadgets 3.在虚拟机中安装这些库,将找到的POP链对象序列化,在反序列化测试payload 4.将序列化之后的payload发送到有漏洞web应用中进行测试.

六、参考

其它不错的PHP反序列化,文章链接: 1、最通俗易懂的PHP反序列化原理分析

https://www.freebuf.com/articles/web/167721.html

2、由Typecho 深入理解PHP反序列化漏洞

https://www.freebuf.com/column/161798.html

3、Typecho install.php 反序列化导致任意代码执行

https://p0sec.net/index.php/archives/114/

4、HP反序列化漏洞成因及漏洞挖掘技巧与案例

https://www.anquanke.com/post/id/84922

免责声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。禁止任何人转载到其他站点,禁止用于任何非法用途。如有任何人凭此做何非法事情,均于笔者无关,特此声明。

最新回复(0)