探究python中pickle,_pickle和pickletools的解析差异问题
字数 1765 2025-08-22 18:37:27
Python中pickle、_pickle和pickletools的解析差异研究
前言
本文深入分析Python中三个与pickle相关的模块:pickle、_pickle和pickletools之间的解析差异。这些差异在正常情况下可能不会引起注意,但在某些特定场景下(如CTF比赛或安全审计)可能成为关键点。
模块概述
- pickle:Python标准库中的纯Python实现
- _pickle:pickle模块的C语言加速实现
- pickletools:pickle的反汇编器和分析工具
关键差异点分析
1. STOP操作码后的栈检查差异
问题描述:
pickletools.dis()会在操作结束后检查栈是否为空,不为空则报错pickle和_pickle则不会进行此检查
利用方法:
构造payload使栈在操作结束后不为空:
payload = b"NN." # NONE, NONE, STOP
pickle和_pickle:成功解析pickletools:报错"stack not empty after STOP"
2. APPENDS/ADDITEMS操作码的零元素检查差异
问题描述:
_pickle(C实现)会检查APPENDS/ADDITEMS操作码是否有零个元素,若有则直接返回pickle(Python实现)不进行此检查,会尝试调用append/add方法
利用方法:
构造payload:
payload = b"N(e." # NONE, MARK, APPENDS, STOP
_pickle:成功解析(零元素直接返回)pickle:报错(尝试对None调用append方法)pickletools:成功解析(仅静态分析)
3. BUILD操作码的state检查差异
问题描述:
_pickle检查state是否为Py_Nonepickle仅检查if state(空字典/元组等也会返回False)
利用方法:
构造payload:
payload = b"]]b." # EMPTY_DICT, EMPTY_DICT, BUILD, STOP
_pickle:报错(空字典被视为有效state)pickle:成功解析(空字典被视为False)pickletools:成功解析
4. MARK操作码的处理差异
问题描述:
pickletools仅模拟操作,不实际执行pickle和_pickle有更严格的运行时检查
利用方法:
构造payload:
payload = b"(." # MARK, STOP
pickletools:成功解析(仅模拟)pickle:报错(尝试从空栈弹出)_pickle:报错(发现意外的MARK)
5. 空字节处理差异
问题描述:
_pickle(C实现)将空字节视为字符串终止符pickle和pickletools会尝试处理整个字符串
利用方法:
构造包含空字节的INT操作:
payload = b"I\x00\x00\x00." # INT with null bytes, STOP
_pickle:成功解析(读取到第一个空字节)pickle和pickletools:报错(无效的整数字面量)
总结表
| 检查点 | pickle | _pickle | pickletools | 关键差异 |
|---|---|---|---|---|
| 1 | 成功 | 成功 | 失败 | pickletools检查栈是否为空 |
| 2 | 失败 | 成功 | 成功 | _pickle检查零元素APPENDS |
| 3 | 成功 | 失败 | 成功 | state检查严格性不同 |
| 4 | 失败 | 失败 | 成功 | pickletools仅静态分析 |
| 5 | 失败 | 成功 | 失败 | 空字节处理方式不同 |
安全启示
- 不要混合使用不同实现进行验证和执行:用pickletools验证后用pickle加载可能导致安全问题
- 注意边界条件检查:不同实现在边界条件(如空集合、空字节等)处理上可能有差异
- 严格输入验证:即使通过了某个实现的验证,也可能在其他实现中导致问题
这些差异在正常情况下可能不会显现,但在安全敏感场景下可能成为漏洞利用的关键点。理解这些差异有助于编写更健壮的代码和进行更有效的安全审计。