Colab

Its fun to be eval

it is said about python that

python gives you enough rope to shoot yourself in both feet

one of the awesomest tools (but also a HUGE potential security issue) is the ability to dynamically run any code. we can do this using the unholy trinity:

  1. eval
  2. exec
  3. compile

we will take a look at some fun things we can do with eval

eval

eval(source, globals=None, locals=None, /)

the eval function (pronounced like EVIL) allows us to evaluate a string as a single expression

An expression in Python is whatever you can have as the value in a variable assignment:

a_variable = (anything you can put within these parentheses is an expression)

eval returns the value of the given expression

a=1
b=2
c=3
x=10
result = eval("a*x**2 + b*x + c")
print(result)

123

Example: parse JSON using eval

a JSON object is basically a javascript expression. as it happens, javascript/JSON syntax is very similar (but not identical 1 ) to python syntax.
many JSON strings can easily be parsed using eval

json_str = """
{
    "firstName": "Jane",
    "lastName": "Doe",
    "hobbies": ["running", "sky diving", "singing"],
    "age": 35,
    "children": [
        {
            "firstName": "Alice",
            "age": 6
        },
        {
            "firstName": "Bob",
            "age": 8
        }
    ]
}
"""

json_obj = eval(json_str)
print(type(json_obj), json_obj.keys())

<class 'dict'> dict_keys(['firstName', 'lastName', 'hobbies', 'age', 'children'])

exec

exec(source, globals=None, locals=None, /)

The exec function allows us to evaluate a string as a statement or series of statements

two famous modules that use exec:

  1. namedtuples
  2. doctest

Note: exec ignores the return value from its code, and always returns None

code = """
x=10
y=20
z=x+y
print(x, '+', y, '=', z)
"""

exec(code)


10 + 20 = 30

controlling the environment

Both exec and eval accept 2 additional positional arguments - globals and locals - which are the global and local variable scopes that the code sees. These default to the globals() and locals() within the scope that called exec or eval, but any dictionary can be used for globals and any mapping for locals (including dict of course). These can be used not only to restrict/modify the variables that the code sees, but are often also used for capturing the variables that the executed code creates

NOTE: exec and eval add the built-ins module as __builtins__ to the globals automatically if it is missing.

Example: rule engine

lets imagine we have some rule-based product, such as a firewall.

furthermore:

  1. we would like to be able to add, modify and delete rules of this product, by changing the configuration of the product, but without changing the source code of the product itself
  2. we would like to be able to write very complex rules, that may needs complicated logic or even loops

to solve for such requirements, we can ask users to write these rules as strings in configuration files, expressed in python langauge.
and to load and exec-ute these strings at runtime from the host product

# rule we read from file
rule = """
if x>100:
    result = 100
elif y<0:
    result = 0
else:
    result = (x+y) / 2 
"""

# create an environment for our rule to run in
globals_ = {}
locals_ = {'x' : 75, 'y': 25}

# run the rule
exec(rule, globals_, locals_)

# get the result
result = locals_['result']
print(result)

50.0

In the example above we used a convention that the rule should output the result of its calculation into the result variable. this is a very simple and effective method of extracting the results from an exec.

it is possible (but requires much more work) to create an rule processing engine that does not require such conventions. one way is to look more deeply into the code we’re executing, such as by using python’s ast abstract syntax trees module

why is exec / eval a security risk?

Consider a situation, where your server runs rules written in text files, and an attacker gets access to modify these text files The attacker could write a rule that imports the os module and then use it to execute arbitrary code on the operating system.

If you allow users to input a value using eval(input()), the user may issue commands to change file or even delete all the files using command os.system(‘rm -rf *’).

If you are using eval(input()) in your code, it’s a good idea to check which variables and methods the user can use. You can see which variables and methods are available using dir() method.

security_risk_code = """
import os
os.system("echo gotcha, I have access to your OS I can delete all your files >> gotcha.txt")
os.system('notepad gotcha.txt')
"""

# exec code - you just lost control
exec(security_risk_code)

We can use the same attack even with the much simpler eval
we just need the expression to itself run an exec code:

security_risk_expression = """\
exec(\"\"\"import os
os.system("echo gotcha, I have access to your OS I can delete all your files >> gotcha.txt")
os.system('notepad gotcha.txt')
\"\"\")
"""

eval(security_risk_expression)

to solve this particular vulnerability, you should explicitly add an empty __builtins__ key to the globals dictionary passed to exec or eval

NOTE: just because we solved THIS security issue, doesn’t mean that there can’t be more security issues, especially as you become more leniant with what the executed code can or can’t do. the only way to be 100% sure we’re not exposed to tricky security issues with eval/exec is to not use them

try:
    exec(security_risk_code, {'__builtins__': {}})
except Exception as ex:
    print('prevented security issue:', ex)
    
try:
    eval(security_risk_expression, {'__builtins__': {}})
except Exception as ex:
    print('prevented security issue:', ex)

prevented security issue: __import__ not found
prevented security issue: name 'exec' is not defined

Further reading

  1. Difference between eval, exec and compile 1
  2. How to get results out of eval 2
  3. How to safely use eval in python 3
  4. Use of exec in python 4