320

I have a situation with some code where eval() came up as a possible solution. Now I have never had to use eval() before but, I have come across plenty of information about the potential danger it can cause. That said, I'm very wary about using it.

My situation is that I have input being given by a user:

datamap = input('Provide some data here: ')

Where datamap needs to be a dictionary. I searched around and found that eval() could work this out. I thought that I might be able to check the type of the input before trying to use the data and that would be a viable security precaution.

datamap = eval(input('Provide some data here: ')
if not isinstance(datamap, dict):
 return

I read through the docs and I am still unclear if this would be safe or not. Does eval evaluate the data as soon as its entered or after the datamap variable is called?

Is the ast module's .literal_eval() the only safe option?

Trenton McKinney
63.1k41 gold badges169 silver badges210 bronze badges
asked Mar 4, 2013 at 8:50
0

6 Answers 6

343

datamap = eval(input('Provide some data here: ')) means that you actually evaluate the code before you deem it to be unsafe or not. It evaluates the code as soon as the function is called. See also the dangers of eval.

ast.literal_eval raises an exception if the input isn't a valid Python datatype, so the code won't be executed if it's not.

Use ast.literal_eval whenever you need eval. You shouldn't usually evaluate literal Python statements.

answered Mar 4, 2013 at 8:52

4 Comments

This isn't 100% correct advice since any bitwise operators (or overloaded operators) will fail. Eg. ast.literal_eval("1 & 1") will throw an error but eval("1 & 1") will not.
Just curious. Shouldn't we use expression parsers or something if we're expecting something like "1 & 1" ?
@thelinuxer you still should, yes; you just wouldn't be able to use ast.literal_eval for something like that (e.g. you could implement a parser manually).
@DanielvanFlymen - to me, your example shows that this is good advice. When you don't want operators (like &) to be evaluated, you use literal_eval. The fact that you can't put arbitrary code there to be executed is a feature, not a bug.
173

ast.literal_eval() only considers a small subset of Python's syntax to be valid:

The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None.

Passing __import__('os').system('rm -rf /a-path-you-really-care-about') into ast.literal_eval() will raise an error, but eval() will happily delete your files.

Since it looks like you're only letting the user input a plain dictionary, use ast.literal_eval(). It safely does what you want and nothing more.

answered Mar 4, 2013 at 8:54

Comments

92

eval: This is very powerful, but is also very dangerous if you accept strings to evaluate from untrusted input. Suppose the string being evaluated is "os.system('rm -rf /')" ? It will really start deleting all the files on your computer.

ast.literal_eval: Safely evaluate an expression node or a string containing a Python literal or container display. The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, None, bytes and sets.

Syntax:

eval(expression, globals=None, locals=None)
import ast
ast.literal_eval(node_or_string)

Example:

# python 2.x - doesn't accept operators in string format
import ast
ast.literal_eval('[1, 2, 3]') # output: [1, 2, 3]
ast.literal_eval('1+1') # output: ValueError: malformed string
# python 3.0 -3.6
import ast
ast.literal_eval("1+1") # output : 2
ast.literal_eval("{'a': 2, 'b': 3, 3:'xyz'}") # output : {'a': 2, 'b': 3, 3:'xyz'}
# type dictionary
ast.literal_eval("",{}) # output : Syntax Error required only one parameter
ast.literal_eval("__import__('os').system('rm -rf /')") # output : error
eval("__import__('os').system('rm -rf /')") 
# output : start deleting all the files on your computer.
# restricting using global and local variables
eval("__import__('os').system('rm -rf /')",{'__builtins__':{}},{})
# output : Error due to blocked imports by passing '__builtins__':{} in global
# But still eval is not safe. we can access and break the code as given below
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,""
 ),{}
)()
)()
"""
eval(s, {'__builtins__':{}})

In the above code ().__class__.__bases__[0] nothing but object itself. Now we instantiated all the subclasses, here our main enter code hereobjective is to find one class named n from it.

We need to code object and function object from instantiated subclasses. This is an alternative way from CPython to access subclasses of object and attach the system.

From python 3.7 ast.literal_eval() is now stricter. Addition and subtraction of arbitrary numbers are no longer allowed. link

Sunit Gautam
6,0953 gold badges20 silver badges34 bronze badges
answered Jan 20, 2016 at 15:56

5 Comments

i am using python 2.7 and i just checked its working fine on python 3.x. My bad i kept trying it on python 2.7
ast.literal_eval("1+1") does not work in python 3.7 and as said before, literal_eval should be limited to literals of those few data structures. It should not be able to parse a binary operation.
Could you explain your KABOOM code, please? Found it here: KABOOM
@winklerrr KABOOM is nicely explained here: nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
ast.literal_eval("1+1") raises ValueError: malformed node or string on line 1 on python 3.10.
58

Python's eager in its evaluation, so eval(input(...)) (Python 3) will evaluate the user's input as soon as it hits the eval, regardless of what you do with the data afterwards. Therefore, this is not safe, especially when you eval user input.

Use ast.literal_eval.


As an example, entering this at the prompt could be very bad for you:

__import__('os').system('rm -rf /a-path-you-really-care-about')
answered Mar 4, 2013 at 8:52

Comments

17

In recent Python3 ast.literal_eval() "no longer parses simple strings"*, instead you are supposed to use the ast.parse() method to create an AST then interpret it.


* UPDATE (Q1:2023): I sometimes get comments about the meaning of "simple strings" in this context. On reading up on the current status I've added this update to try and addresses that.

I wrote this answer some time ago and I used the "simple string" phrase from my reference at the time, sadly I don't recall the source but it was probably getting outdated, but it is true that at one time this method expected something other than a string. So at the time this was a reference to Python 2 and that fact changed slightly in Python 3, but it does come with limitations. Then at some point I updated the code presented from Py2 to Py3 syntax, causing the confusion.

I hope this answer is still a complete example of how to write a safe parser that can evaluate arbitrary expressions under the control of the author that can then be used to interpret uncontrolled data by sanitising each parameter. Comments appreciated as I still use something similar in live projects!

So really the only update is that for very simple Python expressions ast.iteral_eval(str: statements) is now, if I understand correctly, regarded as safe.

And this answer is I hope still a working minimal example of how to implement something similar to ast.literal_eval(str: statements) for a greater diversity of functions, methods and datatypes but still in a simple way that can be considered safe. I am sure there are others methods but that would be out of context as unrelated to the topic of this question.


Here is a complete example of using ast.parse() correctly in Python 3.6+ to evaluate simple arithmetic expressions safely.

import ast
import logging
import math
import operator
logger = logging.getLogger(__name__)
def safe_eval(s):
 def checkmath(x, *args):
 if x not in [x for x in dir(math) if "__" not in x]:
 msg = f"Unknown func {x}()"
 raise SyntaxError(msg)
 fun = getattr(math, x)
 return fun(*args)
 bin_ops = {
 ast.Add: operator.add,
 ast.Sub: operator.sub,
 ast.Mult: operator.mul,
 ast.Div: operator.truediv,
 ast.Mod: operator.mod,
 ast.Pow: operator.pow,
 ast.Call: checkmath,
 ast.BinOp: ast.BinOp,
 }
 un_ops = {
 ast.USub: operator.neg,
 ast.UAdd: operator.pos,
 ast.UnaryOp: ast.UnaryOp,
 }
 ops = tuple(bin_ops) + tuple(un_ops)
 tree = ast.parse(s, mode="eval")
 def _eval(node):
 if isinstance(node, ast.Expression):
 logger.debug("Expr")
 return _eval(node.body)
 if isinstance(node, ast.Constant):
 logger.info("Const")
 return node.value
 if isinstance(node, ast.BinOp):
 logger.debug("BinOp")
 left = _eval(node.left) if isinstance(node.left, ops) else node.left.value
 if isinstance(node.right, ops):
 right = _eval(node.right)
 else:
 right = node.right.value
 return bin_ops[type(node.op)](left, right)
 if isinstance(node, ast.UnaryOp):
 logger.debug("UpOp")
 if isinstance(node.operand, ops):
 operand = _eval(node.operand)
 else:
 operand = node.operand.value
 return un_ops[type(node.op)](operand)
 if isinstance(node, ast.Call):
 args = [_eval(x) for x in node.args]
 return checkmath(node.func.id, *args)
 msg = f"Bad syntax, {type(node)}"
 raise SyntaxError(msg)
 return _eval(tree)
if __name__ == "__main__":
 logger.setLevel(logging.DEBUG)
 ch = logging.StreamHandler()
 logger.addHandler(ch)
 assert safe_eval("1+1") == 2
 assert safe_eval("1+-5") == -4
 assert safe_eval("-1") == -1
 assert safe_eval("-+1") == -1
 assert safe_eval("(100*10)+6") == 1006
 assert safe_eval("100*(10+6)") == 1600
 assert safe_eval("2**4") == 2**4
 assert safe_eval("sqrt(16)+1") == math.sqrt(16) + 1
 assert safe_eval("1.2345 * 10") == 1.2345 * 10
 print("Tests pass")
Wok
5,3838 gold badges47 silver badges73 bronze badges
answered Aug 10, 2021 at 19:34

9 Comments

What if I wanna parse a ast.Lambda, say safe_eval("lambda x: x * 2")? Many thanks
The post is specifically about simple arithmetic evaluation without parsing code, not about parsing Python syntax. If I can do "lambda x: x * 2". Then I could possibly do "lambda x: format_hdd()". Anyway to answer your question, where X is a variable, use safe_eval("X * 2".replace("X", "55")) In my actual application I use f-string like syntax, e.g. safe_eval(f"{X} * 2")
What do you mean by "no longer parses simple strings"? AFAIK, its behaviour hasn't changed across Python versions, like if you look at the docs for 3.0, the description is pretty much the same. It never allowed parsing arbitrary expressions, if that's what you mean, and it never stopped parsing strings, if that's what you mean.
@wjandrea I started in Python 1.x (can't recall what the x was) When revisting old code I found the issue. IIRC, the words are exactly as written in the PEP that removed "simple strings" due to that being a vector for miscreants. I agree I should have linked that refernce when I wrote the answer. But that was some time back. Not sure I could find it again, but I will try for completeness. In any case the answer is still a valid example of how to write a simple interpreter to evaluate expression data, that you have no control over, without compromising security.
@JayM But what does "simple strings" mean?
|
10

If all you need is a user provided dictionary, a possible better solution is json.loads. The main limitation is that JSON dicts ("objects") require string keys. Also you can only provide literal data, but that is also the case for ast.literal_eval.

wjandrea
33.5k10 gold badges69 silver badges104 bronze badges
answered Aug 2, 2017 at 16:42

1 Comment

Why is json.loads better than ast.literal_eval?

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.