Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using ast and whitelists to make python's eval() safe?

OK. I know the experts have spoken and you should not ever use python's eval() on untrusted data, ever. I'm not smarter than the rest of the world, and shouldn't even try this. But! I'm going to, anyhow.

My basic problem is that I'm looking to write a little calculator evaluator program that'll take untrusted input, using a subset of python's syntax. I know: use ply or pyparsing and write a parser and there we go. Screwing around with passing globals and locals to eval() will not do the trick.

All the approaches I've seen (and been leery about) try to enumerate evil. Here, I'm trying to enumerate good -- get an AST, allow only a few node types, and then verify that any calls are to one of a set of whitelisted functions. Here's a mini-implementation (and a gist):

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))

So, my_safe_eval('2*(4+exp(1.3))') works, while my_safe_eval('[].__class__') tricks and my_safe_eval('open("/something/evil")') is likewise forbidden -- without forbidding __builtins__ or __locals__ or anything.

I... I think this works. Am I mad?

like image 269
Nate Avatar asked Sep 21 '12 02:09

Nate


People also ask

Is it safe to use eval in Python?

it depends what the source of the string is that you pass to eval. If your code has generated that string, and you know that it's contents don't call anything malicious then eval is safe.

Why you should never use eval in Python?

eval() is considered insecure because it allows you (or your users) to dynamically execute arbitrary Python code. This is considered bad programming practice because the code that you're reading (or writing) is not the code that you'll execute.

Is literal eval safe?

literal_eval method can safely evaluate strings containing Python values from unknown sources without us having to parse the values. However, complex expressions involving indexing or operators cannot be evaluated using this function.


2 Answers

Zope has a thing called RestrictedPython, you may want to check it, at least to validate your approach or possibly reuse their code. It is configurable and re-usable.

Here's my other answer to a similar question.

like image 62
Sergey Avatar answered Sep 28 '22 06:09

Sergey


Try asteval, seems like the thing you need. Otherwise there is this safe eval

like image 22
Ajay Avatar answered Sep 28 '22 06:09

Ajay