Using eval() safely in python

The eval function lets a python program run python code within itself. It is used in Lybniz evaluate the functions input by the user. Below is simplified example.

#!/usr/bin/env python from math import * user_func = raw_input("type a function: y = ") for x in range(1,10): print "x = ", x , ", y = ", eval(user_func)

The user can type in an expression and it gets evaluated with different values of x.

type a function: y = sin(x/20.0) x = 1 , y = 0.0499791692707 x = 2 , y = 0.0998334166468 x = 3 , y = 0.149438132474 x = 4 , y = 0.198669330795 x = 5 , y = 0.247403959255 x = 6 , y = 0.295520206661 x = 7 , y = 0.342897807455 x = 8 , y = 0.389418342309 x = 9 , y = 0.434965534111

In a large program this can potentially be quite dangerous. Below is an exploitable example

#!/usr/bin/env python from math import * hidden_value = "this is secret" def dangerous_function(filename): print open(filename).read() user_func = raw_input("type a function: y = ") for x in range(1,10): print "x = ", x , ", y = ", eval(user_func)

The user can expose hidden values in the program, or call a dangerous function (dangerous_function("/etc/passwd")). Of course in most cases (desktop programs) the user can't do any more than they could do by writing their own python script, but in some applications (web apps, kiosk computers), this could be a risk.

You can see which functions and variables are available with the dir() function

type a function: y = dir() x = 1 , y = ['__builtins__', '__doc__', '__file__', '__name__', 'acos', 'asin ', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'dangerous_function', 'degrees', 'e' , 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hidden_value', 'hypot', 'ldexp', 'l og', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'ta nh', 'user_func', 'x']

The solution is to restrict this to only the functions and variables you want to make available. eval() takes two extra arguments to allow you to do this: eval(expression[, globals[, locals]]). To do a complete lock down (well almost), you can use

#!/usr/bin/env python from math import * hidden_value = "this is secret" def dangerous_function(filename): print open(filename).read() user_func = raw_input("type a function: y = ") for x in range(1,10): print "x = ", x , ", y = ", eval(user_func,{},{})

Now we can't use any of the math functions, or even the x variable. You might notice that the __builtins__ functions still work (try abs(-1)). To restrict those wee need to use:

print "x = ", x , ", y = ", eval(user_func,{"__builtins__":None},{})

to prevent the __builtins__ creeping back in.

The next job is to fill the local name space with functions and variables that are needed. If there are just a few they can be done fairly manually:

print "x = ", x , ", y = ", eval(user_func,{"__builtins__":None},{"x":x,"sin":sin})

Now sin(x) will work again, however hidden_value will not.

If a lot of functions are needed there are easier ways to generate a safe namespace for the eval function. In Lybniz a list of safe functions is made, and used to filter the existing namespace.

#!/usr/bin/env python from math import * hidden_value = "this is secret" def dangerous_function(filename): print open(filename).read() #make a list of safe functions safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'de grees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] #use the list to filter the local namespace safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ]) #add any needed builtins back in. safe_dict['abs'] = abs user_func = raw_input("type a function: y = ") for x in range(1,10): # add x in safe_dict['x']=x print "x = ", x , ", y = ", eval(user_func,{"__builtins__":None},safe_dict)

All the code examples can be downloaded here

Back to Lybniz


SourceForge.net Logo