设为首页 收藏本站
查看: 892|回复: 0

[经验分享] 用RestrictedPython创建Python Sandbox

[复制链接]

尚未签到

发表于 2017-4-25 10:29:09 | 显示全部楼层 |阅读模式
众所周知在Python下可以用eval和exec来执行任意的python表达式及脚本,但是,当运行的脚本来自于网络时这样做就会有安全风险,你总不想运行包含"import os; os.system('rm -rf /')"的脚本吧?对这类问题,通常的解决方法是为来自网络的脚本创建一个沙箱(Sandbox),浏览器在执行来自网络的Java Applet时会创建一个权限较低的Sandbox,在其中运行的代码不能做任何威胁系统的操作,例如读取/写入本地文件,执行外部程序等。Python运行环境并不原生支持创建Sandbox,它不像java在执行危险操作会先进行权限检查,而RestrictedPython提供了一种可选的创建sandbox的方式。
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, '<string>', 'exec')
exec code in _globals

输出:

Traceback (most recent call last):
File "sandbox.py", line 24, in <module>
exec code in _globals
File "<string>", line 2, in <module>
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, '<string>', 'exec')
exec code in _globals

输出:

Traceback (most recent call last):
File "sandbox.py", line 9, in <module>
exec code in _globals
File "<string>", line 1, in <module>
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, '<string>', 'exec')
_globals = {
'_getattr_': _hook_getattr,
}
exec code in _globals

输出:

Traceback (most recent call last):
File "sandbox.py", line 19, in <module>
exec code in _globals
File "<string>", line 4, in <module>
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, '<string>', 'exec')
_globals = {
'Person': Person,
'_write_': full_write_guard,
}
exec code in _globals

输出:

Traceback (most recent call last):
File "sandbox.py", line 22, in <module>
exec code in _globals
File "<string>", line 7, in <module>
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, '<string>', 'exec')
_globals = {
'Person': Person,
'_write_': _hook_write,
}
exec code in _globals

输出:

Traceback (most recent call last):
File "sandbox.py", line 24, in <module>
exec code in _globals
File "<string>", line 3, in <module>
File "sandbox.py", line 12, in _hook_write
raise RuntimeError('cannot modify person object')
RuntimeError: cannot modify person object


完。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-368987-1-1.html 上篇帖子: python import模块方法 下篇帖子: Python模块结构布局
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表