Django Code Injection via exec()

CRITICAL

User input flows to exec(), enabling arbitrary Python statement execution on the server.

Rule Information

Language
Python
Category
Django
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythondjangocode-injectionexecrcetaint-analysisinter-proceduralCWE-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-DJANGO-SEC-021 --project .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
39
40
41

About This Rule

Understanding the vulnerability and how it is detected

This rule detects code injection vulnerabilities in Django applications where untrusted user input from HTTP request parameters flows into Python's exec() function.

Python's exec() executes its string argument as arbitrary Python statements, including imports, function definitions, class definitions, assignments, and any other Python code. Unlike eval() which is limited to expressions, exec() can execute complete programs. When user-controlled data reaches exec(), an attacker can inject any Python code to execute with the full privileges of the application process.

exec() is more powerful than eval() because it accepts statements (not just expressions), enabling attackers to define persistent functions, import modules, modify global state, and execute multi-line attack payloads. The same sandbox escape techniques that break eval() restrictions also apply to exec().

Django applications should never call exec() with user-controlled input. If dynamic code execution is genuinely needed for a use case, it should be implemented in a sandboxed subprocess with no access to application secrets or the database.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Full Remote Code Execution with Persistent State

exec() can define functions, modify global variables, and inject persistent code into the application's runtime. An attacker could redefine Django view functions, add malicious middleware, or modify model methods -- changes that persist until the process restarts.

2

Multi-Statement Attack Payloads

Unlike eval() which is limited to single expressions, exec() accepts multi-line Python code including loops, conditional blocks, and exception handlers. This allows attackers to write sophisticated attack scripts that can probe the environment, conditionally exfiltrate data, and cover their tracks.

3

Module Import and Capability Expansion

exec() can execute import statements, giving attackers access to any Python module available in the environment. exec("import socket; socket.connect(...)") or exec("import subprocess; subprocess.run(...)") are trivial attack payloads once exec() with user input is reachable.

4

Data Corruption and Database Manipulation

With access to Django's ORM through exec(), an attacker can query, modify, or delete any database record. They can also access Django's settings module to extract database credentials and use them to connect directly to the database outside the application.

How to Fix

Recommended remediation steps

  • 1Remove all exec() calls that process user-controlled input; replace with explicit allowlist dispatch patterns using pre-defined functions.
  • 2If dynamic behavior is needed, encode it as data (configuration, feature flags, rule sets) rather than as executable code from user input.
  • 3For plugin or extension systems, load code from trusted, authenticated sources on the filesystem, never from HTTP request parameters.
  • 4Use Django's URL routing, middleware, and signal systems for dynamic dispatch rather than exec() or eval().
  • 5If untrusted code must be executed for testing or educational purposes, use a fully isolated subprocess with no access to application secrets, a read-only filesystem, and network isolation.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule performs inter-procedural taint analysis with global scope. Sources include calls("request.GET.get"), calls("request.POST.get"), calls("request.GET.__getitem__"), calls("request.POST.__getitem__"), calls("request.body"), and calls("request.read"). The sink is calls("exec") with tainted input tracked via .tracks(0). There are no recognized sanitizers for exec() -- any user-controlled input reaching exec() is a confirmed critical vulnerability. The rule follows taint across file and module boundaries including through string concatenation and f-string construction.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-95 - Eval Injection in Most Dangerous Software Weaknesses list
OWASP Top 10
A03:2021 - Injection
PCI DSS v4.0
Requirement 6.2.4 - protect against injection attacks
NIST SP 800-53
SI-10: Information Input Validation; CM-7: Least Functionality
SOC 2 Type II
CC6.1 - Logical access controls to prevent unauthorized data access

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Django Code Injection via exec()

eval() is limited to Python expressions (things that produce a value), while exec() executes arbitrary statements including import, def, class, for, while, and assignment statements. exec() injection is therefore more powerful: attackers can define persistent functions, import modules, and execute multi-line programs. Both are critical vulnerabilities; exec() is slightly worse due to the statement- level access it provides.
Options from safest to most complex: (1) Replace the scripting feature with a configuration-based system where users configure behavior via structured data (JSON rules, feature flags) rather than code. (2) Restrict available operations to a fixed set of pre-defined actions users can combine. (3) If code execution is truly required, run scripts as separate isolated subprocess calls with no access to the application's Django process, environment, or database credentials.
No. try/except catches exceptions but the injected code already executes before any exception could be raised. An attacker can write code that produces no exception while still reading secrets, making outbound network connections, or modifying application state. Exception handling is not a security control for code injection.
Database-stored code is a form of second-order injection. If an attacker can write to the database through another vulnerability (e.g., insufficient access controls, another injection vulnerability), they can plant malicious code that exec() later executes. Never execute code from the database with exec() or eval().
exec() is sometimes used for dynamic code generation for performance optimization (e.g., NumPy uses it internally for generated C extensions). In web applications, this use case almost never applies to user-facing request handling. If exec() is used for code generation from static templates, ensure the templates never include user-controlled values in code positions.
exec() with user input is one of the most severe findings a penetration test can report -- it is Direct Remote Code Execution. It will always be classified as Critical severity. Any audit, compliance review, or bug bounty program will treat it as an immediate remediation priority that blocks deployment.

New feature

Get these findings posted directly on your GitHub pull requests

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

See how it works