Python eval() Function

Spread the love

The eval() function is a built-in Python function used to evaluate a string as a Python expression and return its result. It effectively lets you run Python code that’s stored as a string.

Syntax:

eval(expression, globals=None, locals=None)

Parameters:

  • expression: This mandatory argument is the string to be evaluated.
  • globals: Optional dictionary to define the global namespace for the execution.
  • locals: Optional dictionary to define the local namespace for the execution.

Return Value

The eval( ) function returns the result evaluated from the expression.

Basic Usage of eval( )

The eval() function stands for “evaluate”. It takes a string argument that contains a valid Python expression and evaluates or “executes” that expression. The result of this evaluation is then returned.

Basic Arithmetic Operations:

If you have a string containing a simple arithmetic expression, you can use eval() to calculate its value.

expression = "3 + 4"
result = eval(expression)
print(result)  # Outputs: 7

The string “3 + 4” is interpreted and evaluated as a sum operation, yielding the result 7.

Variable Evaluations:

eval() can also recognize and evaluate variables that exist in the current scope.

x = 5
expression = "x * 10"
result = eval(expression)
print(result)  # Outputs: 50

Here, the string “x * 10” refers to the variable x which has been defined previously in the code. The expression multiplies x by 10, producing the result 50.

Complex Expressions:

More complex expressions, such as those involving functions available in the current scope, can also be evaluated.

expression = "pow(2, 3)"  # This will calculate 2 raised to the power of 3
result = eval(expression)
print(result)  # Outputs: 8

In the above snippet, the pow function is used to compute 2^3, which yields 8.

Some Considerations:

String Format: The expression passed to eval() must be a string. If you pass a non-string value, Python will raise a TypeError.

# This will cause an error
result = eval(5 + 7)

Valid Python Expression: The string passed to eval() must form a valid Python expression. Statements like for, if, import, etc., aren’t expressions and will result in a SyntaxError when passed to eval().

# This will cause an error
expression = "for i in range(5): print(i)"
eval(expression)

Return Value: The eval() function returns the value that the given expression evaluates to. If the expression doesn’t have a return value (for instance, if it’s an assignment), then Python will raise an error.

# This will cause an error
expression = "x = 10"
eval(expression)

At its core, the basic usage of eval() is straightforward: it evaluates a string as a Python expression and returns the result. This provides a dynamic way to execute code in Python, making it especially useful in scenarios where you might need to evaluate code generated at runtime.

However, while its usage can be simple, it’s essential to be cautious. When dealing with user input or other untrusted sources, blindly using eval() can introduce security risks, as it can evaluate potentially harmful commands. Always ensure that the strings passed to eval() are sanitized and safe to execute.

Using with globals and locals

Understanding how globals and locals work with the eval() function is crucial, especially when you’re working with dynamic expressions that rely on variable names or want to restrict the available namespaces for the evaluated code.

What are globals and locals ?

  • globals: This is a dictionary representing the current global namespace (i.e., all the global variables and functions).
  • locals: This is a dictionary representing the current local namespace (i.e., all the local variables inside a function or a method).

When used with the eval() function, these dictionaries can define or restrict the variables and functions available to the evaluated expression.

Default Behavior:

By default, if you don’t specify globals and locals, eval() uses the current scope’s global and local namespace.

x = 10
y = 20

result = eval("x + y")
print(result)  # Outputs: 30

Here, the variables x and y are recognized from the current global scope.

Using globals :

The globals parameter allows you to specify global variables that should be accessible during the evaluation.

x = 10

# Using eval without specifying globals
print(eval("x"))  # This will print: 10

# Using eval with specifying a different value for x in globals
print(eval("x", {"x": 20}))  # This will print: 20

In the above example, we have a global variable x with a value of 10. In the second use of eval(), we specify a different value for x (i.e., 20), and that value is used during the evaluation.

Using locals :

When inside a function, you have both global and local variables. The locals parameter lets you control the local variables that should be accessible.

x = 10

def example_function():
    y = 5
    # Here, x is global and y is local
    print(eval("x + y"))  # This will print: 15
    
    # Using eval with a different value for y in locals
    print(eval("x + y", {"x": 20}, {"y": 7}))  # This will print: 27

example_function()

In the function, y is a local variable with a value of 5. When we use eval(), we override the values of both x and y to 20 and 7, respectively.

Restricting Access:

You can limit what’s accessible by eval() using globals and locals.

x = 10

# Using eval with an empty globals dictionary
# This means eval has no knowledge of any global variables
print(eval("x", {}))  # This will raise a NameError because x is not accessible

In this example, by passing an empty dictionary, we’re telling eval() that there are no global variables it can access.

Final Thoughts:

When you’re using eval(), the globals and locals parameters give you a way to:

  1. Override the values of variables during the evaluation.
  2. Control which variables are accessible to the evaluated expression.

By mastering these, you can have better control and safety when working with dynamic code evaluation in Python.

Vulnerability Issues With eval( )

The eval() function, while a powerful tool in Python, can introduce significant security vulnerabilities if not used with caution.

Arbitrary Code Execution:

The primary concern with eval() is that it can evaluate arbitrary Python code. If an attacker can supply or influence the string passed to eval(), they can make it execute malicious code.

user_input = input("Enter a mathematical expression: ")
result = eval(user_input)

In the above code, if a user inputs a string like "os.system('rm -rf /')" (assuming the os module was imported), it would lead to the execution of a potentially harmful system command.

Exposure of Sensitive Information:

An attacker can use eval() to access sensitive information in the program’s environment.

secret_key = "SECRET123"
user_input = input("Enter an expression: ")
print(eval(user_input))

If a user inputs "secret_key", the program will print out the secret value.

Resource Exhaustion:

An attacker might input code that exhausts system resources, causing Denial of Service (DoS). For instance, "([x*x for x in range(1, 10000000)])" would attempt to create a massive list, potentially causing the program to run out of memory.

How to make eval( ) safe

Avoid eval( ) Wherever Possible:

Rationale: At its core, eval() executes raw Python code. This raw execution capability can quickly become a double-edged sword, especially when the source of the code (the string passed to eval()) is influenced by external input or unclear sources.

Alternative Approach: Instead of dynamically executing code, try to leverage static methods to achieve your objectives. For instance:

  • If you want to allow users to perform mathematical calculations, you might opt for a dedicated mathematical parser library.
  • For deserializing trusted data, consider using JSON or another format that can be safely parsed without code execution.

Sanitize Input:

Rationale: One of the main threats with eval() is malicious or malformed input. By sanitizing input, you aim to strip away or reject any data that isn’t strictly needed or expected.

How-To:

  • Use regular expressions or string methods to whitelist allowed characters or patterns.
  • Explicitly reject any input that contains Python keywords or suspicious strings like “__” (which might be used to access __builtins__, __import__, etc.).
  • Remember, blacklisting (defining what’s not allowed) is generally more error-prone than whitelisting (defining what’s allowed).

Restrict Available Namespaces:

Rationale: By default, eval() has access to all global and local variables and can also access built-in modules and functions. You can reduce this access, which can limit the potential damage of an unsafe eval() call.

How-To:

  • Use the globals and locals arguments of eval() to control its access to variables and functions.
  • For maximum restriction, pass an empty dictionary or explicitly remove access to dangerous functions:
safe_globals = {"__builtins__": None}
result = eval(expression, safe_globals)

Use literal_eval for Safe Evaluations:

Rationale: Many times, developers use eval() simply to convert a string representation of a basic Python data type (like lists or dictionaries) back into the actual data type. literal_eval provides a safe way to do this without risking code execution.

How-To:

Instead of eval(), use:

from ast import literal_eval
result = literal_eval(some_string)

Remember, literal_eval() is limited to evaluating basic Python data types. It cannot evaluate complex expressions or function calls.

Consider Context:

Rationale: Sometimes, the potential risks of using eval() can be somewhat offset by the broader context. For example, if you’re working in an isolated environment or with fully trusted data, the risks are reduced.

How-To:

  • Still practice caution. Even in trusted environments, future changes or oversights might introduce unexpected risks.
  • Always document clearly when and why you’re using eval(), so future developers or maintainers are aware of its presence and associated risks.

Wrapping Up:

Mitigation strategies for eval() revolve around two core principles: reducing the potential for malicious input and limiting the power of eval() when it’s executed. By combining multiple strategies, you can further layer your defenses and reduce associated risks.

Best Practices and Alternatives

  1. Limit Scope with globals and locals:When using eval(), restrict its scope by passing empty dictionaries for the globals and locals arguments. This way, even if there’s potentially harmful code in the string, it won’t have access to any functions or variables.
  2. Sanitize Input:If you must use user-provided strings with eval(), sanitize the input to ensure that only safe and expected values are passed.
  3. Consider Alternatives:
    • For mathematical expressions, libraries like SymPy or mathexpr can evaluate strings in a safer environment.
    • If you’re parsing data formats like JSON, use dedicated parsers (json.loads()) instead of eval().
    • For more complex use-cases, consider implementing a domain-specific language (DSL) that restricts the potential actions.

Conclusion

The eval() function in Python offers a powerful way to evaluate strings as Python expressions. While it has valid use-cases, especially in controlled environments or for quick-and-dirty scripting tasks, developers should approach it with caution. Understanding its risks, limitations, and potential alternatives is crucial for safely leveraging its capabilities. Whether you decide to use eval() or one of its safer alternatives, always prioritize the security and maintainability of your code.

Leave a Reply