RestrictedPython原理
RestrictedPython的关键在于使用compile_restricted去编译Python代码,它和内置compile差不多也是将源代码编译成exec或eval可以直接运行的AST(语法抽象树)。和compile不一样的地方在于,compile_restricted悄悄地改变地生成的AST,它将读/写属性、访问/更新字典变成可以hook的函数调用。例如,它将x.foo变成_getattr_(x, 'foo'),将x.foo = 'bar'变成_write_(x).foo = 'bar',将x['foo']变成_getitem_(x, 'foo'),x['foo'] = 'bar'变成_write_(x)['foo'] = 'bar'。通过提供你自己的_getattr_,_write_,_getitem_等函数,你可以控制运行脚本所能干的事情,即为脚本创建一个Sandbox。这种创建Sandobx方式有些类似于Java中的AspectJ,通过改变生成的java的字节码来增加类的行为。
1. 控制导入
要控制导入只需要定制__import__函数,这是python的机制,和RestrictedPython无关,如果你不需要复杂的控制,通过定制__import__,控制__builtins__导入的变量,并在globals中导入一些需要的变量或模块来实现。要实现不能导入os模块的效果,可以这样做:
src = ''' import os ''' _builtins = dict(__builtins__.__dict__) def _hook_import(name, *args, **kwargs): if name == 'os': # now allow to import os raise RuntimeError('cannot import os') # otherwise, use default __import__ return __import__(name, *args, **kwargs) # replace __import__ with our hook implementation _builtins['__import__'] = _hook_import _globals = { '__builtins__': _builtins, } # use builtin compile instead of compile_restricted code = compile(src, '', 'exec') exec code in _globals
输出:
Traceback (most recent call last): File "sandbox.py", line 24, inexec code in _globals File " ", line 2, in File "sandbox.py", line 16, in _hook_import raise RuntimeError('cannot import os') RuntimeError: cannot import os
上面的代码并没有使用任何RestrictedPython相关的函数。通过在_hook_import直接抛出异常可以实现不允许导入任何模块的效果,或者更直接的方式,从__builtins__直接删除__import__。RestrictedPython提供了safe_builtins字典,它去除了任何可能导致安全问题的内置函数,比如open, getattr/setattr, __import__。
from RestrictedPython.Guards import safe_builtins _globals = dict(__builtins__=safe_builtins) src = '''open('/etc/passwd')''' code = compile(src, '', 'exec') exec code in _globals
输出:
Traceback (most recent call last): File "sandbox.py", line 9, inexec code in _globals File " ", line 1, in NameError: name 'open' is not defined
2. 控制属性读取
控制属性读取需要定制_getattr_(或_getitem_),它接受两个参数,第一个参数是要访问的对象,第二个参数是访问的属性名称(或索引值)。通过这个它我们可以实现让脚本只能读取某些对象的某些属性,需要注意方法调用也是属性访问,foo.bar()会变成_getattr(foo, 'bar')()。要不让脚本访问os的name属性及调用os.system()方法,可以这样做:
from RestrictedPython import compile_restricted def _hook_getattr(obj, attr): import os if obj is os and attr in ('name', 'system'): raise RuntimeError('cannot invoke os.name or os.system') return getattr(obj, attr) src = ''' import os #os.name os.system('ls') ''' code = compile_restricted(src, '', 'exec') _globals = { '_getattr_': _hook_getattr, } exec code in _globals
输出:
Traceback (most recent call last): File "sandbox.py", line 19, inexec code in _globals File " ", line 4, in File "sandbox.py", line 7, in _hook_getattr raise RuntimeError('cannot invoke os.name or os.system') RuntimeError: cannot invoke os.name or os.system
定制_getitem_是类似的,不再赘述。
3. 控制属性写入
控制属性写入需要定制_write_方法,它接受单个对象,要允许对象写入任何属性,直接将原对象返回,要不允许写入任何属性(包括更新dict及list元素),直接抛出异常,如果只允许部分属性写入,则需要返回修改后的对象,在设置它的属性时做检查。RestrictedPython提供full_write_guard,它不允许修改除了dict和list之外的任何对象。
from RestrictedPython import compile_restricted from RestrictedPython.Guards import full_write_guard class Person(object): name = 'marlon' src = ''' d = { } d['foo'] = 'bar' # OK l = [1, 2] l[0] = 'egg' # OK p = Person() p.name = 'john' # ERROR! ''' code = compile_restricted(src, '', 'exec') _globals = { 'Person': Person, '_write_': full_write_guard, } exec code in _globals
输出:
Traceback (most recent call last): File "sandbox.py", line 22, inexec code in _globals File " ", line 7, in File "xxx/RestrictedPython/Guards.py", line 101, in handler raise TypeError, error_msg TypeError: attribute-less object (assign or del)
要使得只是不能修改Person的属性,则可以这样:
from RestrictedPython import compile_restricted class Person(object): name = 'marlon' def _hook_write(obj): if not isinstance(obj, Person): return obj else: raise RuntimeError('cannot modify person object') src = ''' p = Person() p.name = 'john' # ERROR! ''' code = compile_restricted(src, '', 'exec') _globals = { 'Person': Person, '_write_': _hook_write, } exec code in _globals
输出:
Traceback (most recent call last): File "sandbox.py", line 24, inexec code in _globals File " ", line 3, in File "sandbox.py", line 12, in _hook_write raise RuntimeError('cannot modify person object') RuntimeError: cannot modify person object
完。