大约在原问题提出十年之后,Python 3.8.0 推出了审计功能。它有助于解决问题吗?为简单起见,让我们将讨论限制在硬盘写入方面并进行探讨:
from sys import addaudithook
def block_mischief(event,arg):
if 'WRITE_LOCK' in globals() and ((event=='open' and arg[1]!='r')
or event.split('.')[0] in ['subprocess', 'os', 'shutil', 'winreg']): raise IOError('file write forbidden')
addaudithook(block_mischief)
到目前为止,exec
可以轻松地将内容写入磁盘:
exec("open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')", dict(locals()))
但是我们可以随意禁止它,这样没有恶意用户可以从exec()
提供的代码访问磁盘。像numpy
或pickle
这样的Python程序库最终会使用Python的文件访问,因此它们也被禁止进行磁盘写入。外部程序调用也已被明确禁用。
WRITE_LOCK = True
exec("open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')", dict(locals()))
exec("open('/tmp/FILE','a').write('pwned by l33t h4xx0rz')", dict(locals()))
exec("numpy.savetxt('/tmp/FILE', numpy.eye(3))", dict(locals()))
exec("import subprocess; subprocess.call('echo PWNED >> /tmp/FILE', shell=True)", dict(locals()))
尝试在
exec()
内部删除锁定似乎是徒劳无功的,因为审计钩子使用的是不可访问
exec
运行的代码的不同
locals
副本。请证明我错了。
在exec()
内部尝试删除锁定似乎是徒劳无功的,因为审计钩子使用的是不可访问exec
运行的代码的不同locals
副本。请证明我错了。
exec("print('muhehehe'); del WRITE_LOCK; open('/tmp/FILE','w')", dict(locals()))
...
OSError: file write forbidden
当然,顶层代码可以重新启用文件I/O。
del WRITE_LOCK
exec("open('/tmp/FILE','w')", dict(locals()))
在Cpython内部进行沙箱隔离一直非常困难,许多先前的尝试都失败了。这种方法也并不完全安全,例如对于公共网络访问:
也许使用直接操作系统调用的假设编译模块无法由Cpython审核 - 建议白名单安全的纯Python模块。
Cpython解释器仍然可能崩溃或过载。
也许还存在某些漏洞可以将文件写入硬盘。但我没有使用任何通常的沙箱逃避技巧来写入单个字节。我们可以说Python生态系统的“攻击面”缩小到了一个相当狭窄的(被禁止/允许)事件列表:https://docs.python.org/3/library/audit_events.html
我会感激任何指出这种方法缺陷的人。
编辑:所以这也是不安全的! 我非常感谢@Emu使用异常捕获和内省的聪明黑客攻击:
from sys import addaudithook
def block_mischief(event,arg):
if 'WRITE_LOCK' in globals() and ((event=='open' and arg[1]!='r') or event.split('.')[0] in ['subprocess', 'os', 'shutil', 'winreg']):
raise IOError('file write forbidden')
addaudithook(block_mischief)
WRITE_LOCK = True
exec("""
import sys
def r(a, b):
try:
raise Exception()
except:
del sys.exc_info()[2].tb_frame.f_back.f_globals['WRITE_LOCK']
import sys
w = type('evil',(object,),{'__ne__':r})()
sys.audit('open', None, w)
open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')""", dict(locals()))
我认为审计和子处理是正确的方法,但不要在生产机器上使用:
https://bitbucket.org/fdominec/experimental_sandbox_in_cpython38/src/master/sandbox_experiment.py