I have been tasked with building an application where an end user can have custom rules to evaluate whether a returned query results in a warning or alert (based on there own thresholds).
I've built a way for the user to template their logic. An example looks like this:
if (abs(<<21>>) >= abs(<<22>>)):
retVal = <<21>>
else:
retVal = <<22>>
The <<21>>
and <<22>>
parameters will be substituted with values found earlier in the program. Once all this substitution occurs I have a very simple if/else block (in this example) that looks like this stored in a variable (execCd
):
if (abs(22.0) >= abs(-162.0)):
retVal = 22.0
else:
retVal = -162.0
This will exec()
correctly. Now, how can I secure this? I've looked at this article: http://lybniz2.sourceforge.net/safeeval.html
My code ends up looking like this:
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']
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])
safe_dict['abs'] = abs
exec(execCd,{"__builtins__":None},safe_dict)
However, the exec fails when I have the second and third parameter with this exception - NameError: name 'retVal' is not defined
Some of the custom logic the end users have is extensive and much of this changes on a fairly regular basis. I don't want to maintain their custom logic and end users want to be able to test various warning/alert threshold logic quickly.
How can I secure this exec statement from unsafe (either intentional or unintentional) code?
Python's built-in exec() function allows you to execute arbitrary Python code from a string or compiled code input. The exec() function can be handy when you need to run dynamically generated Python code, but it can be pretty dangerous if you use it carelessly.
The globals() and locals() functions returns the current global and local dictionary, respectively, which may be useful to pass around for use by eval() or execfile().
The exec() function executes the specified Python code. The exec() function accepts large blocks of code, unlike the eval() function which only accepts a single expression.
The only safe way to use eval
or exec
is not to use them.
You do not need to use exec. Instead of building a string to execute, parse it into objects, and use that to drive your code execution.
At its simplest, you can store functions in a dict, and use a string to select the function to call. If you're using python syntax, python provides all the utilities to parse itself, and you should use those.
Your exec
statement isn't adding retVal to your local environment, but to the safe_dict
dictionary. So you can get it back from there:
execCd = """
if (abs(22.0) >= abs(-162.0)):
retVal = 22.0
else:
retVal = -162.0
"""
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']
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])
safe_dict['abs'] = abs
exec(execCd,{"__builtins__":None},safe_dict)
retVal = safe_dict["retVal"]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With