目录

  1. 1. 前言
  2. 2. 生成器
    1. 2.1. 生成器表达式
    2. 2.2. 生成器的属性
  3. 3. 栈帧
  4. 4. 利用栈帧沙箱逃逸
  5. 5. 相关题目

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Python利用栈帧沙箱逃逸

2024/4/29 Web 沙箱逃逸 python
  |     |   总文章阅读量:

前言

参考:

https://xz.aliyun.com/t/13635

https://zhuanlan.zhihu.com/p/637003652


生成器

生成器(Generator)是 Python 中一种特殊的迭代器,它可以通过简单的函数和表达式来创建。

生成器的主要特点是能够逐个产生值,并且在每次生成值后保留当前的状态,以便下次调用时可以继续生成值。这使得生成器非常适合处理大型数据集或需要延迟计算的情况。

在 Python 中,生成器可以使用yield关键字来定义。yield 用于产生一个值,并在保留当前状态的同时暂停函数的执行。当下一次调用生成器时,函数会从上次暂停的位置继续执行,直到遇到下一个 yield 语句或者函数结束。

def f():
    a=1
    while True:
        yield a
        a+=1
f=f()
print(next(f)) #输出1
print(next(f)) #输出2
print(next(f)) #输出3

什么意思呢?这里实现了惰性计算,即仅在需要时才生成值,而不是一次性生成整个序列


生成器表达式

一种在 Python 中创建生成器的紧凑形式,类似于列表推导式

语法与列表推导式([表达式 for 变量 in 列表] )类似,但是使用圆括号而不是方括号

生成器表达式会逐个生成值,而不是一次性生成整个序列,这样可以节省内存空间,特别是在处理大型数据集时非常有用

在一个有逻辑范围的情况下下可以通过生成器表达式去实现,如计数1-100:

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

生成器的属性

  • gi_code:生成器对应的 code 对象
  • gi_frame:生成器对应的 frame(栈帧)对象
  • gi_running:生成器函数是否在执行。生成器函数在yield以后、执行yield的下一行代码前处于frozen状态,此时这个属性的值为0
  • gi_yieldfrom:如果生成器正在从另一个生成器中 yield 值,则为该生成器对象的引用;否则为 None
  • gi_frame.f_locals:一个字典,包含生成器当前帧的本地变量

对于gi_frame,这是一个与生成器(generator)和协程(coroutine)相关的属性。它指向生成器或协程当前执行的帧对象(frame object),如果这个生成器或协程正在执行的话。帧对象表示代码执行的当前上下文,包含了局部变量、执行的字节码指令等信息。

使用生成器的gi_frame属性来获取生成器的当前帧信息:

def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()

# 获取生成器的当前帧信息
frame = gen.gi_frame

# 输出生成器的当前帧信息
print("Local Variables:", frame.f_locals)
print("Global Variables:", frame.f_globals)
print("Code Object:", frame.f_code)
print("Instruction Pointer:", frame.f_lasti)

image-20240429010312936


栈帧

栈帧(stack frame)是Python虚拟机执行的载体之一,它是一个数据结构,用于存储代码运行时的数据

每个栈帧都有自己的局部变量、操作数栈、异常处理信息等

栈帧是一个栈结构,每个栈帧都有一个指向上一个栈帧的指针,这样就形成了一个栈帧链

栈帧的创建和销毁是由Python虚拟机自动完成的,当一个函数被调用时,就会创建一个栈帧,当函数返回时,就会销毁这个栈帧

其数据结构:

typedef struct _frame PyFrameObject;

struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL 前一个栈帧 */
    PyCodeObject *f_code;       /* code segment */      // 代码段
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) 内置符号表,字典 */
    PyObject *f_globals;        /* global symbol table (PyDictObject) 全局符号表 字典 */
    PyObject *f_locals;         /* local symbol table (any mapping)  当前作用域 映射 */
    PyObject **f_valuestack;    /* points after the last local */   // 指向最后一个局部变量的后面
    PyObject *f_trace;          /* Trace function */    // 跟踪函数
    int f_stackdepth;           /* Depth of value stack */  // 值栈深度
    char f_trace_lines;         /* Emit per-line trace events? */ // 每行跟踪事件
    char f_trace_opcodes;       /* Emit per-opcode trace events? */ // 每个操作码跟踪事件

    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;        // 生成器

    int f_lasti;                /* Last instruction if called */    // 最后一条指令
    int f_lineno;               /* Current line number. Only valid if non-zero */   // 当前行号
    int f_iblock;               /* index in f_blockstack */ // f_blockstack的索引
    PyFrameState f_state;       /* What state the frame is in 状态 */   // 栈帧的状态
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks  */   // try 和 loop 块,长度为CO_MAXBLOCKS=20
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */   // 局部变量和栈,动态地长度
};

这里我们直接看几个关键的属性:

  • f_locals:一个字典,包含了函数或方法的局部变量。键是变量名,值是变量的值
  • f_globals:一个字典,包含了函数或方法所在模块的全局变量。键是全局变量名,值是变量的值
  • f_code:一个代码对象(code object),包含了函数或方法的字节码指令、常量、变量名等信息
  • f_lasti:整数,表示最后执行的字节码指令的索引
  • f_back:指向上一级调用栈帧的引用,用于构建调用栈

利用栈帧沙箱逃逸

原理:通过生成器的栈帧对象通过f_back(返回前一帧)从而逃逸出去获取globals全局符号表

demo:

s3cret="this is flag"

codes='''
def waff():
    def f():
        yield g.gi_frame.f_back

    g = f()  #生成器
    frame = next(g) #获取到生成器的栈帧对象
    print(frame)
    print(frame.f_back)
    print(frame.f_back.f_back)
    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"])

image-20240429011524042

类似于nodejs原型链污染里的__proto__,逐步往外取得栈帧,从而得到外部的全局globals,最终拿到1.py里的全局变量s3cret

这里也可以使用f_locals去代替f_globals效果是相同的,但是要注意,locals返回的是局部符号表,它包含了在当前函数或方法内部定义的变量。这些局部变量只在当前函数或方法的执行过程中存在,并且只能在该函数或方法内部访问。当函数执行完毕后,这些局部变量就会被销毁。


相关题目

L3HCTF 2024 —— intractable problem