我的评估实施完全安全吗? [英] Is my eval implementation completely safe?
问题描述
我有一个代码,我想让用户通过stdin传递三个值,每个值的类型为 float
, Fraction
或 int
,因此我无法对输入应用 ast.literal_eval
来获得所需的结果。
I have a code, in which I want let the user to pass through stdin three values, each of type float
, Fraction
or int
, so I can't apply ast.literal_eval
on input to get result I want.
我读了评估真的很危险,显示
-
评估 c的弱点如何被那些给出输入,
- 一些使
eval
调用的方法更安全。
- how
eval
's weaknesses can be used by person who gives input, and - some ways how to make
eval
invokes more safe.
我应用了作者的建议并编写了这段代码,替换了默认的 eval
:
I applyed author's advices and wrote this code, replacing default eval
:
import builtins
class DoubleUnderscoreInEval(ValueError):
pass
def eval(expression, globals={}, locals=None):
if '__' in expression:
raise DoubleUnderscoreInEval('Using __ in eval is not allowed for safety reasons.')
else:
if '__builtins__' not in globals:
globals['__builtins__']={}
return builtins.eval(expression, globals, locals)
通过我的代码使用 builtins.eval
完全安全吗? < br>
如果没有,我可以使 eval
调用完全安全吗? (鼓励使用字符串来规避我的限制)
如果是,怎么办?如果没有,那我该怎么用呢?
Is using builtins.eval
through my code completely safe?
If no, can I make eval
invokes completely safe? (string circumventing my restrictions is encouraged)
If yes, how? If no, what can I use instead?
我是Python的新手,因此鼓励您提出任何有关如何改善代码的注释。
I'm beginner in Python, so any comments telling how can I improve my code are encouraged.
推荐答案
不,您的 eval()
实现是不安全的。其他对象也可以访问 __ builtins __
映射。
No, your eval()
implementation is not safe. Other objects can give access to the __builtins__
mapping too.
请参见评估很危险(内德·巴切尔德(Ned Batchelder))。
See eval is dangerous by Ned Batchelder for some examples.
例如,以下字符串可能导致段错误:
For example, the following string can cause a segfault:
s = """
(lambda fc=(
lambda n: [
c for c in
().__class__.__bases__[0].__subclasses__()
if c.__name__ == n
][0]
):
fc("function")(
fc("code")(
0,0,0,0,"KABOOM",(),(),(),"","",0,""
),{}
)()
)()
"""
我担心仅禁止双下划线是不够的。某些有进取心的灵魂会找到一种以您意想不到的创造性方式重新组合双下划线的方法,并且您会发现自己无论如何都被黑了。
Merely disallowing double underscores is not going to be enough, I fear. Some enterprising soul will find a method of re-combining double underscores in a creative way you didn't anticipate and you'll find yourself hacked anyway.
ast.literal_eval()
方法使用AST解析和对parsetree的自定义处理来支持内置类型。您可以通过添加对安全可调用对象的支持来做到这一点:
The ast.literal_eval()
method uses AST parsing and custom handling of the parsetree to support built-in types. You could do the same to by adding support for 'safe' callables:
import ast
def literal_eval_with_callables(node_or_string, safe_callables=None):
if safe_callables is None:
safe_callables = {}
if isinstance(node_or_string, str):
node_or_string = ast.parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
try:
# Python 3.4 and up
ast.NameConstant
const_test = lambda n: isinstance(n, ast.NameConstant)
const_extract = lambda n: n.value
except AttributeError:
# Everything before
_const_names = {'None': None, 'True': True, 'False': False}
const_test = lambda n: isinstance(n, ast.Name) and n.id in _const_names
const_extract = lambda n: _const_names[n.id]
def _convert(node):
if isinstance(node, (ast.Str, ast.Bytes)):
return node.s
elif isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.Tuple):
return tuple(map(_convert, node.elts))
elif isinstance(node, ast.List):
return list(map(_convert, node.elts))
elif isinstance(node, ast.Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
elif const_test(node):
return const_extract(node)
elif isinstance(node, ast.UnaryOp) and \
isinstance(node.op, (ast.UAdd, ast.USub)) and \
isinstance(node.operand, (ast.Num, ast.UnaryOp, ast.BinOp)):
operand = _convert(node.operand)
if isinstance(node.op, ast.UAdd):
return + operand
else:
return - operand
elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (ast.Add, ast.Sub)) and \
isinstance(node.right, (ast.Num, ast.UnaryOp, ast.BinOp)) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, (ast.Num, ast.UnaryOp, astBinOp)):
left = _convert(node.left)
right = _convert(node.right)
if isinstance(node.op, ast.Add):
return left + right
else:
return left - right
elif isinstance(node, ast.Call) and \
isinstance(node.func, ast.Name) and \
node.func.id in safe_callables:
return safe_callables[node.func.id](
*[_convert(n) for n in node.args],
**{kw.arg: _convert(kw.value) for kw in node.keywords})
raise ValueError('malformed string')
return _convert(node_or_string)
上述函数改编了 ast.literal_eval()
的实现以添加对特定内容的支持已注册的可调用项。传递命名为 Fraction
的字典:
The above function adapts the implementation of ast.literal_eval()
to add support for specific registered callables. Pass in a dictionary naming Fraction
:
>>> import fractions
>>> safe_callables = {'Fraction': fractions.Fraction}
>>> literal_eval_with_callables('Fraction(1, denominator=2)', safe_callables)
Fraction(1, 2)
上述方法将白名单而不是黑名单处理。例如,允许调用 Fraction
,并且直接控制要调用的对象。
The above method whitelists rather than blacklists what will be handled. Calling Fraction
is allowed, and directly controlled exactly what object will be called, for example.
演示:
>>> samples = '''\
... 2, -9, -35
... 4, -13, 3
... -6, 13, 5
... 5, -6, 6
... -2, 5, -3
... 4, 12, 9
... 0.5, 1, 1
... 1, -0.5, -0.5
... 0.25, Fraction(-1, 3), Fraction(1, 9)'''.splitlines()
>>> safe_callables = {'Fraction': fractions.Fraction}
>>> for line in samples:
... print(literal_eval_with_callables(line, safe_callables))
...
(2, -9, -35)
(4, -13, 3)
(-6, 13, 5)
(5, -6, 6)
(-2, 5, -3)
(4, 12, 9)
(0.5, 1, 1)
(1, -0.5, -0.5)
(0.25, Fraction(-1, 3), Fraction(1, 9))
以上内容至少应在Python 3.3及更高版本上可用。 Python 2需要更多工作来支持 unicode
字符串,并且不会在 ast.Bytes
上中断。
The above should work on at least Python 3.3 and up, possibly earlier. Python 2 would require some more work to support unicode
strings and not break on ast.Bytes
.
这篇关于我的评估实施完全安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!