Python’s eval() Function: A Powerful Tool with Security Risks
Python is a popular programming language with various applications, from scientific computing to machine learning. One of Python’s most useful built-in functions is eval()
, which lets you evaluate expressions within your Python code. This function can evaluate Boolean expressions, mathematical expressions, and general-purpose expressions, making it a versatile tool for both novice and experienced programmers.
What is eval()
?
At its core, eval()
is a Python function that evaluates an expression. This expression can be a string or a code object and can contain variables, function calls, and operators.
When eval()
is called, Python interprets the expression and returns the result. The first argument of eval()
is the expression to be evaluated. This can be a string containing the expression or a code object compiled from the expression. For example, you could use eval()
to calculate the result of a mathematical expression, such as “2+2,” by passing it as a string to the eval()
function.
The second argument of eval()
is the globals dictionary. This dictionary contains global variables accessible from within the evaluated expression. This allows you to pass in variables defined in your code to the evaluated expression. The third argument of eval()
is the locals dictionary. This dictionary contains local variables accessible from within the evaluated expression. This allows you to pass in variables defined within the evaluated expression itself.
Boolean Expressions
Boolean expressions are used to evaluate whether a certain condition is true or false. These expressions can include comparison operators such as >, <, ==, and !=, as well as logical operators such as and, or, and not.
For example, you could use eval()
to evaluate whether a number is greater than 10 by passing in an expression such as “number > 10.”
Math Expressions
Math expressions are used to perform mathematical calculations, such as addition, subtraction, multiplication, and division. These expressions can also include parentheses to control the order of operations.
For example, you could use eval()
to calculate the result of a mathematical expression such as “((5+6)*2)-3.”
General-Purpose Expressions
General-purpose expressions can include various operators and functions and can be used for a wide range of purposes. For example, you could use eval()
to concatenate two strings by passing in an expression such as “‘hello’ + ‘world'”.
You could also use eval()
to call a function, such as “function_name(argument1, argument2)”. When using eval()
, it is important to be aware of the security risks associated with evaluating user input. Since eval()
can evaluate any expression, including expressions provided by users, it can be vulnerable to code injection attacks. To mitigate these risks, it is recommended to only use eval()
with trusted expressions that have been thoroughly validated.
Securing eval()
Python’s eval()
function is a powerful tool often used to evaluate expressions but can also pose security risks. Hackers use techniques such as code injection to exploit the function and compromise security. Fortunately, Python provides several ways to reduce these security concerns.
Restricting globals and locals
One way to minimize the security issues of eval()
is to restrict the use of globals and locals. Globals refers to the variables defined in the program, while locals are variables defined within the eval()
function. Typically, when eval()
is called, it allows access to all these variables and functions. However, by providing only a limited subset of these variables, we can reduce the risk of code injection.
To restrict access to these variables, we can pass a dictionary of allowed global and local variables as an argument to eval()
. By restricting the variables, we can limit the user’s input to prevent damage, even if it’s malicious.
A more secure method is to use namespaces, which allow you to have several dictionaries of variables, and you can choose to restrict some of them before running eval()
.
Restricting the Use of Built-In Names
Python’s built-in names are methods and functions provided by Python in the built-in namespace. These functions can be potentially used by attackers to gain control over the program. Therefore, it is vital to limit the usage of these functions to only trusted sources when calling them with eval()
. To restrict the use of built-in names, you can use the __builtin__
module, which contains all the built-in functions for the Python interpreter.
In Python 3.x, you can use the built-in module that is built for this specific purpose. You need to build a dictionary of allowed functions and set the built-in module’s functions to this dictionary. This enables you to enforce policies limiting the use of these potentially dangerous methods.
Restricting Names in the Input
When using eval()
with input(), the user can potentially inject code and compromise its security. To avoid this, we need to sanitize the input, allowing only safe and trusted code for evaluation within the function.
One way of doing this is by mapping the input to a set of allowed strings, making sure no malicious code can slip in. Another method of restricting the names in the input is to use the ast
module, which can parse and evaluate an expression without executing it. Thus, it can check the input expression’s validity before passing it to eval()
. The ast
module also provides a whitelist of names used in the input, allowing only the allowed names to be used.
Restricting the Input to Only Literals
A literal is a value expressed exactly as it was intended to be interpreted by Python. These include strings, integers, floating-point numbers, and others. By restricting eval()
to accept only literals, we can prevent the user from injecting and running malicious code. To limit eval()
to only process literals, we can use the literal_eval()
function included in the ast
module. This function evaluates only literals and returns their value. Code injections are not allowed, and any code that doesn’t conform to the strict literal syntax will raise an exception.
Building a Math Expressions Evaluator
Using all the techniques, we mentioned, we can create an evaluator that is safe and efficient. Here is an example of a simple evaluator:
import ast
import operator
allowed_globals = {'__builtins__': None, 'abs': abs, 'round': round, 'math': None}
def evaluate(expression):
tree = ast.parse(expression, mode='eval')
allowed_names = {node.id for node in ast.walk(tree) if isinstance(node, ast.Name)}
for name in allowed_names:
if name not in allowed_globals:
raise NameError('Name not allowed: {name}')
code = compile(tree, '
return eval(code, allowed_globals)
This math expression evaluator creates a new dictionary of allowed global variables. The function then inserts the abs
and round
functions and a global module, math
, from which mathematical functions can be accessed. It parses the expression given using the ast.parse()
method. You can check specifically for allowed variables such as names and functions before compiling and executing the code using eval()
.
Conclusion
The eval()
function is useful for dynamically evaluating code, but it can pose significant security risks if you don’t take extra measures to secure it. Restricting accesses to variables and namespaces, using the ast
module to restrict names and literals as inputs, and creating a whitelist of allowed built-ins are some methods you can use to help reduce security concerns.
By carefully controlling the functionality of eval()
, it still remains very powerful and useful for a wide range of applications. Nonetheless, the importance of securing this function cannot be overstated, and it is essential to remain cautious while using it.