我们都知道eval
is dangerous,即使你隐藏危险的函数,因为你可以使用Python的内省功能深入挖掘并重新提取它们。例如,即使您删除__builtins__
,也可以使用
[c for c in ().__class__.__base__.__subclasses__()
if c.__name__ == 'catch_warnings'][0]()._module.__builtins__
但是,我见过的每个例子都使用属性访问。如果我禁用所有内置函数,和禁用属性访问(通过使用Python令牌化程序对输入进行标记,并在具有属性访问令牌时拒绝它),该怎么办?
在你问之前,不,对于我的用例,我不需要其中任何一个,所以它不会太瘫痪。
我想要做的是让SymPy的sympify功能更安全。目前,它对输入进行了标记,对其进行了一些转换,并在命名空间中对其进行了演绎。但它不安全,因为它允许属性访问(即使它确实不需要它)。
答案 0 :(得分:25)
我将提到Python 3.6的一个新功能 - f-strings。
他们可以评估表达式,
>>> eval('f"{().__class__.__base__}"', {'__builtins__': None}, {})
"<class 'object'>"
但Python的tokenizer不会检测到属性访问:
0,0-0,0: ENCODING 'utf-8'
1,0-1,1: ERRORTOKEN "'"
1,1-1,27: STRING 'f"{().__class__.__base__}"'
2,0-2,0: ENDMARKER ''
答案 1 :(得分:19)
如果您尝试{{1},可以从eval
构建一个返回值,该值会在异常 之外 eval
},print
,log
,任何事情:
repr
这会创建一个eval('''((lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))))
(lambda f: lambda n: (1,(1,(1,(1,f(n-1))))) if n else 1)(300))''')
形式的嵌套元组;该值不能(1,(1,(1,(1...
(在Python 3上),print
ed或str
ed;所有调试它的尝试都会导致
repr
RuntimeError: maximum recursion depth exceeded while getting the repr of a tuple
和pprint
也失败了:
saferepr
因此没有安全的内置函数来对此进行字符串化:可以使用以下帮助程序:
...
File "/usr/lib/python3.4/pprint.py", line 390, in _safe_repr
orepr, oreadable, orecur = _safe_repr(o, context, maxlevels, level)
File "/usr/lib/python3.4/pprint.py", line 340, in _safe_repr
if issubclass(typ, dict) and r is dict.__repr__:
RuntimeError: maximum recursion depth exceeded while calling a Python object
然后问题是 2 中的 def excsafe_repr(obj):
try:
return repr(obj)
except:
return object.__repr__(obj).replace('>', ' [exception raised]>')
实际上并没有使用print
/ str
,所以你由于缺乏递归检查而没有任何安全性。也就是说,取上面的lambda怪物的返回值,你不能repr
,str
它,但普通的repr
(不是print
!)打印得很好。但是,如果您知道将使用print_function
语句打印它,可以利用它在Python 2上生成SIGSEGV:
print
使用SIGSEGV崩溃Python 2 。 This is WONTFIX in the bug tracker。因此,如果您想要安全,请不要使用print eval('(lambda i: [i for i in ((i, 1) for j in range(1000000))][-1])(1)')
- 语句。 print
!
这不是崩溃,而是
from __future__ import print_function
运行时,输出
eval('(1,' * 100 + ')' * 100)
可以捕获s_push: parser stack overflow
Traceback (most recent call last):
File "yyy.py", line 1, in <module>
eval('(1,' * 100 + ')' * 100)
MemoryError
,是MemoryError
的子类。解析器有一些really conservative limits to avoid crashes from stackoverflows(双关语)。但是,Exception
由C代码输出到s_push: parser stack overflow
,无法抑制。
就在昨天我问why doesn't Python 3.4 be fixed for a crash from,
stderr
和Serhiy Storchaka's answer证实Python核心开发人员不会将看似格式良好的代码的SIGSEGV视为安全问题:
3.4仅接受安全修复程序。
因此可以得出结论,在Python中执行来自第三方的任何代码都是安全的,无论是否已经过消毒。
然后Nick Coghlan然后added:
由于Python代码引发的分段错误原因的一些额外背景目前不被视为安全漏洞:由于CPython不包含安全沙箱,我们已经完全依赖操作系统来提供进程隔离。 操作系统级安全边界不受代码是“正常”运行的影响,也不受故意触发的分段故障后的修改状态的影响。
答案 2 :(得分:11)
用户仍然可以通过输入一个计算结果很大的表达式来帮助你,这会填满你的内存并导致Python进程崩溃,例如
'10**10**100'
如果可以在这里进行更复杂的攻击,例如恢复内置或创建段错误,我仍然很好奇。
编辑:
事实证明,即使是Python的解析器也存在这个问题。
lambda: 10**10**100
会挂起,因为它会尝试预先计算常量。
答案 3 :(得分:7)
我认为Python不会针对不受信任的代码设置任何安全性。这是在官方Python 2解释器中通过堆栈溢出(在C堆栈上)引发段错误的简单方法:
eval('()' * 98765)
从我的answer到“返回SIGSEGV的最短代码”Code Golf问题。
答案 4 :(得分:1)
这是一个safe_eval示例,它将确保计算的表达式不包含不安全的标记。 它不会尝试使用literal_eval方法来解释AST,而是将令牌类型列入白名单,并在表达式通过测试时使用真实eval。
# license: MIT (C) tardyp
import ast
def safe_eval(expr, variables):
"""
Safely evaluate a a string containing a Python
expression. The string or node provided may only consist of the following
Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
and None. safe operators are allowed (and, or, ==, !=, not, +, -, ^, %, in, is)
"""
_safe_names = {'None': None, 'True': True, 'False': False}
_safe_nodes = [
'Add', 'And', 'BinOp', 'BitAnd', 'BitOr', 'BitXor', 'BoolOp',
'Compare', 'Dict', 'Eq', 'Expr', 'Expression', 'For',
'Gt', 'GtE', 'Is', 'In', 'IsNot', 'LShift', 'List',
'Load', 'Lt', 'LtE', 'Mod', 'Name', 'Not', 'NotEq', 'NotIn',
'Num', 'Or', 'RShift', 'Set', 'Slice', 'Str', 'Sub',
'Tuple', 'UAdd', 'USub', 'UnaryOp', 'boolop', 'cmpop',
'expr', 'expr_context', 'operator', 'slice', 'unaryop']
node = ast.parse(expr, mode='eval')
for subnode in ast.walk(node):
subnode_name = type(subnode).__name__
if isinstance(subnode, ast.Name):
if subnode.id not in _safe_names and subnode.id not in variables:
raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode.id))
if subnode_name not in _safe_nodes:
raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode_name))
return eval(expr, variables)
class SafeEvalTests(unittest.TestCase):
def test_basic(self):
self.assertEqual(safe_eval("1", {}), 1)
def test_local(self):
self.assertEqual(safe_eval("a", {'a': 2}), 2)
def test_local_bool(self):
self.assertEqual(safe_eval("a==2", {'a': 2}), True)
def test_lambda(self):
self.assertRaises(ValueError, safe_eval, "lambda : None", {'a': 2})
def test_bad_name(self):
self.assertRaises(ValueError, safe_eval, "a == None2", {'a': 2})
def test_attr(self):
self.assertRaises(ValueError, safe_eval, "a.__dict__", {'a': 2})
def test_eval(self):
self.assertRaises(ValueError, safe_eval, "eval('os.exit()')", {})
def test_exec(self):
self.assertRaises(SyntaxError, safe_eval, "exec 'import os'", {})
def test_multiply(self):
self.assertRaises(ValueError, safe_eval, "'s' * 3", {})
def test_power(self):
self.assertRaises(ValueError, safe_eval, "3 ** 3", {})
def test_comprehensions(self):
self.assertRaises(ValueError, safe_eval, "[i for i in [1,2]]", {'i': 1})
答案 5 :(得分:0)
控制locals
和globals
字典非常重要。否则,有人可以传递eval
或exec
,并以递归方式调用
safe_eval('''e("""[c for c in ().__class__.__base__.__subclasses__()
if c.__name__ == \'catch_warnings\'][0]()._module.__builtins__""")''',
globals={'e': eval})
递归eval
中的表达式只是一个字符串。
您还需要将全局命名空间中的eval
和exec
名称设置为不是真实eval
或exec
的名称。全局命名空间很重要。如果你使用本地命名空间,任何创建单独命名空间的东西,比如comprehensions和lambdas,都会解决它的问题
safe_eval('''[eval("""[c for c in ().__class__.__base__.__subclasses__()
if c.__name__ == \'catch_warnings\'][0]()._module.__builtins__""") for i in [1]][0]''', locals={'eval': None})
safe_eval('''(lambda: eval("""[c for c in ().__class__.__base__.__subclasses__()
if c.__name__ == \'catch_warnings\'][0]()._module.__builtins__"""))()''',
locals={'eval': None})
同样,在这里,safe_eval
只能看到一个字符串和一个函数调用,而不是属性访问。
如果它有一个禁用安全解析的标志,你还需要清除safe_eval
函数本身。否则你可以简单地做
safe_eval('safe_eval("<dangerous code>", safe=False)')