Flask Code Injection via eval()

CRITICAL

User input from Flask request parameters flows to eval(). Replace with ast.literal_eval() for data parsing or json.loads() for structured input.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskcode-injectionevaleval-injectionrcecross-fileinter-proceduraltaint-analysisCWE-95OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-FLASK-SEC-004 --project .
1
2
3
4
5
6
7
8
9
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

About This Rule

Understanding the vulnerability and how it is detected

This rule detects eval injection in Flask applications where user-controlled input from HTTP request parameters reaches Python's built-in eval() function. eval() evaluates its argument as a Python expression and returns the result. When the argument contains attacker-controlled data, the attacker can inject arbitrary Python expressions including function calls, attribute access, and import statements that lead to remote code execution.

Unlike OS command injection, eval injection executes within the Python interpreter itself. The attacker has direct access to Python builtins, imported modules, and the application's own namespaces. Common payloads access __import__('os').system() or use __builtins__ to invoke any Python functionality available in the process.

The taint analysis traces data from Flask request sources through variable assignments and function calls to the eval() sink at argument position 0. The .tracks(0) parameter means only the expression string argument is tracked -- the optional globals and locals dictionaries at positions 1 and 2 are not analyzed by this rule. Flows through ast.literal_eval() or json.loads() are recognized as sanitizers because these functions evaluate only safe literal types (strings, numbers, lists, dicts, tuples, booleans, None) without executing arbitrary code.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Direct Remote Code Execution in the Python Interpreter

eval() has access to Python builtins by default. An attacker can call __import__('subprocess').check_output(['id']) to execute OS commands, __import__('socket') to establish network connections, or access the application's database session objects via the interpreter's global namespace. There is no sandbox -- the attacker operates with the full privileges of the Flask process.

2

Secret and Credential Extraction

The Python runtime holds application secrets in memory: database passwords, API keys, JWT secrets, and OAuth tokens. An injected expression like globals()['app'].config['SECRET_KEY'] or os.environ['DB_PASSWORD'] extracts these values and returns them in the HTTP response body.

3

Bypassing Authentication and Authorization

eval() can call application functions directly. An attacker who knows the codebase (from source code exposure or error messages) can invoke admin functions, reset passwords, or elevate privileges by calling internal Python functions through the eval() context.

4

Supply Chain Attacks via Injected Imports

__import__() inside eval() can load any installed Python package. In environments with broad pip dependencies, attackers use eval injection to instantiate classes from installed libraries in unexpected ways, reaching attack surface that is not directly accessible from the Flask route handlers.

How to Fix

Recommended remediation steps

  • 1Replace eval() with ast.literal_eval() when you need to parse Python literals (numbers, strings, lists, dicts) -- it rejects any expression that is not a safe literal type.
  • 2Use json.loads() for structured data exchange instead of eval() -- JSON is a strict subset with no callable constructs.
  • 3If you need to evaluate mathematical expressions, use a dedicated safe math library like simpleeval or numexpr instead of Python's eval().
  • 4Never pass eval() a default globals or locals dict that contains application internals -- attackers can walk the object graph to reach sensitive state.
  • 5Audit all uses of eval() in the codebase regardless of whether they currently receive user input -- refactor them out before new code paths introduce tainted data.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

Scope: global (cross-file taint tracking across the entire project). Sources: Flask HTTP input methods -- request.args.get(), request.form.get(), request.values.get(), request.get_json() -- all of which return attacker-controlled strings that can contain Python expression syntax. Sinks: eval(). The .tracks(0) parameter focuses on argument position 0, the expression string. The optional globals and locals dictionaries at positions 1 and 2 are separate concerns and are not tracked by this rule. Sanitizers: ast.literal_eval() and json.loads() are recognized as sanitizing transformations. A value that passes through either function before reaching eval() is treated as safe because those functions constrain the input to non-executable data types. The rule follows tainted values through assignments, return values, and cross-file function calls, catching eval() injections in utility modules and helper functions that receive data from Flask route handlers.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A03:2021 - Injection
CWE Top 25
CWE-95 -- Eval Injection in Dangerous Software Weaknesses list
PCI DSS v4.0
Requirement 6.2.4 -- prevent injection attacks including code injection
NIST SP 800-53
SI-10: Information Input Validation
SOC 2 Type II
CC6.1 -- logical and physical access controls preventing unauthorized code execution

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask Code Injection via eval()

ast.literal_eval() evaluates only Python literals: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None. It raises ValueError for anything that contains a function call, attribute access, or import statement. It is safe for parsing structured data but is not a general-purpose alternative to eval() -- if you need math expressions or more complex evaluation, use a dedicated safe expression library.
No. ast.literal_eval() parses the input into an AST and then evaluates only nodes that correspond to literal types. If the AST contains any non-literal node, it raises ValueError before evaluation. There are no known bypasses. The risk is calling ast.literal_eval() on the result of a manipulation that turns a literal into an expression -- always apply it directly to the raw user input.
The taint analysis follows data flow regardless of call depth or class structure. If request.args.get() feeds a value through a class method, a decorator, or a nested function that eventually calls eval(), the rule traces the full chain. Scope boundaries within the same Python module and across imported modules are both handled.
Use simpleeval (pip install simpleeval) for arithmetic expression evaluation. It supports operators and basic math functions but restricts the expression language to a safe subset. For spreadsheet-style formulas, formulaic is another option. Both are designed specifically for safe user-defined expression evaluation and do not expose Python's object model.
Start by determining what the input is supposed to contain. If it is a number, use int() or float(). If it is a Python literal (list, dict, tuple), use ast.literal_eval(). If it is JSON, use json.loads(). If it is a mathematical expression, use simpleeval. If no safe alternative covers the use case, the feature needs to be redesigned to not require arbitrary expression evaluation.
No. The rule only flags flows where the argument to eval() is reachable from a Flask request source (request.args.get, etc.). eval('2 + 2') with a hardcoded string literal does not create a tainted flow and will not be flagged.
No. compile() followed by exec() is covered by PYTHON-FLASK-SEC-005, which tracks flows to exec() and compile() separately. SEC-004 covers eval() only. Run both rules together for complete coverage of Python code injection patterns.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask Code Injection via eval() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works