python反序列化基础
python反序列通常会用Pickle组件进行操作,和python中的json转换一样,使用loads和dumps2个函数实现反序列化和序列化操作
1 | import pickle |
原理与PHP反序列化差不多其实
简单代码:
1 | import pickle |
1. 代码逐行分析
1.1 导入依赖库
python
复制
1 | import pickle |
- **
pickle**:Python内置的序列化模块,用于将对象转换为字节流(序列化)或从字节流重建对象(反序列化)。 - **
base64**:用于将二进制数据编码为ASCII字符串,便于在网络或文本环境中传输。
1.2 定义恶意类 Exploit
python
复制
1 | class Exploit: |
__reduce__方法:- 在对象被序列化时,若类定义了此方法,
pickle会调用它。 - 返回值必须是一个元组,格式为
(callable, args),其中:- **
callable**:可调用对象(如函数、类)。 - **
args**:传递给可调用对象的参数(必须是元组)。
- **
- 此处返回的
callable是subprocess.check_output(用于执行系统命令并捕获输出),args是命令列表['cat', '/flag']。
- 在对象被序列化时,若类定义了此方法,
1.3 生成Payload并编码
python
复制
1 | payload = pickle.dumps(Exploit()) # 序列化对象为字节流 |
- **
pickle.dumps()**:将Exploit类的实例序列化为二进制数据(字节流)。 - **
base64.b64encode()**:将二进制数据编码为Base64字符串,确保其可通过文本协议(如HTTP)传输。
2. 攻击原理
2.1 反序列化过程触发代码执行
当服务端对接收到的Base64字符串进行反序列化(如调用 pickle.loads())时,会执行以下操作:
- 重建
Exploit对象:根据序列化数据调用__reduce__方法。 - **执行
subprocess.check_output(['cat', '/flag'])**:- **
subprocess.check_output()**:执行系统命令cat /flag,并返回命令的标准输出(即文件内容)。 - **
cat /flag**:读取目标文件/flag的内容(假设CTF的flag存储在此路径)。
- **
2.2 结果回显
如果服务端在反序列化后将结果返回给用户(例如打印到响应中),你将在输出中直接看到
/flag文件的内容。示例输出:
复制
1
反序列化成功: b'CTF{this_is_a_fake_flag}\n'
3. 关键点解析
3.1 为何 subprocess.check_output 有效?
- 命令执行:
check_output会执行给定的命令,并捕获其输出(以字节流形式返回)。 - 无Shell依赖:命令参数以列表形式传递(如
['cat', '/flag']),避免Shell注入风险,同时确保参数正确解析。
3.2 为何需要Base64编码?
- 传输安全:二进制序列化数据可能包含不可打印字符,Base64编码后仅包含字母、数字和
+/=符号,适合通过HTTP请求或表单提交。
3.3 为何 __reduce__ 方法是关键?
- Hook点:
pickle在反序列化时会自动调用__reduce__,使得攻击者可以注入任意代码执行逻辑。
4. 防御与风险
4.1 为何这种攻击有效?
- 设计缺陷:
pickle反序列化本质上是“重建对象时执行任意代码”,无法验证数据来源的合法性。 - 服务端信任输入:若服务端反序列化了用户可控的未经验证的数据,攻击即可成功。
4.2 如何防御此类攻击?
- 禁止反序列化不可信数据:最根本的解决方案。
- 使用安全序列化格式:如JSON、XML(不涉及代码执行)。
- 签名验证:对序列化数据签名,确保未被篡改。
5. 完整攻击流程
- 构造Payload:生成包含恶意
Exploit类的序列化数据。 - Base64编码:转换为可传输的文本格式。
- 提交Payload:通过HTTP请求或其他接口发送给服务端。
- 触发攻击:服务端反序列化数据,执行
cat /flag。 - 获取结果:从响应中提取flag。