Usando ast e whitelists para tornar o eval () do python seguro?
ESTÁ BEM. Euconhecer a peritos ter falado e você não deveriasempre usar pythoneval()
em dados não confiáveis, nunca. Eu não sou mais esperto do que o resto do mundo, e nem deveria tentar isso. Mas! Eu vou, de qualquer forma.
Meu problema básico é que eu estou olhando para escrever um pequeno programa de avaliador de calculadoras que receberá uma entrada não confiável, usando um subconjunto da sintaxe do python. Eu sei: usedobra oupyparsing e escreva um analisador e lá vamos nós. Aparafusando com globals passando e locals paraeval()
não vai fazer o truque.
Todas as abordagens que tenho visto (e tem sido desconfiado) tentam enumerar o mal. Aqui estou tentando enumerarBoa - obtenha um AST, permita apenas alguns tipos de nó e verifique se todas as chamadas são para um conjunto de funções na lista de permissões. Aqui está uma mini-implementação (euma essência):
import ast
import math
SAFE_FX = {
'exp': math.exp,
}
SAFE_NODES = set(
(ast.Expression,
ast.Num,
ast.Call,
ast.Name,
ast.Load,
ast.BinOp,
ast.Add,
ast.Sub,
ast.Mult,
ast.Div,)
)
class CleansingNodeVisitor(ast.NodeVisitor):
def generic_visit(self, node):
if type(node) not in SAFE_NODES:
raise Exception("%s not in SAFE_NODES" % type(node))
super(CleansingNodeVisitor, self).generic_visit(node)
def visit_Call(self, call):
if call.func.id not in SAFE_FX:
raise Exception("Unknown function: %s" % call.func.id)
def my_safe_eval(s):
tree = ast.parse(s, mode='eval')
cnv = CleansingNodeVisitor()
cnv.visit(tree)
compiled = compile(tree, s, "eval")
return(eval(compiled, SAFE_FX))
Assim,my_safe_eval('2*(4+exp(1.3))')
funciona, enquantomy_safe_eval('[].__class__')
truques emy_safe_eval('open("/something/evil")')
é igualmente proibido - sem proibir__builtins__
ou__locals__
ou nada.
Eu acho que isso funciona. Eu estou louco?