Description
Python's eval() and exec() do more than convert strings. eval() interprets a string as a Python expression, while exec() interprets it as a suite of Python statements. Both execute with the application's privileges. Passing untrusted text, such as an HTTP request value, to either function lets an attacker supply Python code that changes the application's control flow.
Restricting global and local namespaces or changing the __builtins__ mapping does not create a secure sandbox. ast.literal_eval() does not execute Python code, but Python documents memory, stack and CPU exhaustion risks. It is not a general replacement for handling hostile input.
Potential impact
- Reading, modifying or leaking data accessible to the application
- Changing application state and control flow
- Using file, network or operating-system functions available to the process
- Interrupting service through exceptions, unbounded computation or excessive resource use
Executing commands does not automatically grant higher operating-system privileges. The impact depends on the process's permissions, available modules, secrets and network access.
Remediation
- Remove dynamic evaluation. For a defined data format such as JSON, use
json.loads(), limit request size, validate the parsed structure, types and ranges, and treat the result only as data. JSON parsing can also consume excessive resources on large input. - If users must choose an operation, map a small set of server-owned identifiers to predefined callables and invoke the selected object directly. Do not insert an allowed name back into a string passed to
eval()orexec(). - Do not treat restricted globals, modified
__builtins__, simple string replacement or a helper namedsanitizeas a security boundary. - If running untrusted Python is an explicit product requirement, isolate it from the application process in a hardened runtime with limits on CPU, memory, execution time, filesystem and network access, and privileges.
Examples
Before
from flask import Flask, request
app = Flask(__name__)
@app.get("/evaluate")
def evaluate():
expression = request.args.get("expression", "")
result = eval(expression) # Unsafe: execute request text as Python code
return {"result": str(result)}
After
from flask import Flask, abort, request
app = Flask(__name__)
def service_status():
return "healthy"
def application_version():
return "2.0"
OPERATIONS = {
"status": service_status,
"version": application_version,
}
@app.get("/operation")
def run_operation():
operation = OPERATIONS.get(request.args.get("name", ""))
if operation is None:
abort(400, description="Unsupported operation")
return {"result": operation()} # Call the object registered by the server directly
The safer example does not interpret request values as Python syntax. The value is only a key in a server-defined mapping, and the selected callable runs directly.
References
- Python documentation:
eval() - Python documentation:
exec() - Python documentation:
ast.literal_eval() - Python
jsonimplementation limitations - CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code
- CWE-94: Improper Control of Generation of Code
- OWASP Top 10:2025 A05 Injection
- OWASP Top 10:2021 A03 Injection
- OWASP Code Injection
- OWASP ASVS 5.0.0 V1 Encoding and Sanitization