Python code injection

Python code injection through eval and exec

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() or exec().
  • Do not treat restricted globals, modified __builtins__, simple string replacement or a helper named sanitize as 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

python
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

python
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