Django Command Injection via os.system()

CRITICAL

User input flows to os.system(), enabling arbitrary OS command execution with the privileges of the Django process.

Rule Information

Language
Python
Category
Django
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythondjangocommand-injectionos-systemshelltaint-analysisinter-proceduralCWE-78OWASP-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-010 --project .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
42
43

About This Rule

Understanding the vulnerability and how it is detected

This rule detects OS command injection vulnerabilities in Django applications where untrusted user input from HTTP request parameters flows into os.system() calls.

os.system() executes its string argument through the system shell (typically /bin/sh on Unix). When user-controlled data is embedded in the command string, an attacker can inject shell metacharacters (semicolons, pipes, backticks, $() substitution) to execute arbitrary commands. The injected commands run with the full privileges of the Django application process, which on misconfigured servers may include file system access, network access, or even root privileges.

os.system() is particularly dangerous because it always invokes the shell, unlike subprocess.run() with a list argument which can bypass the shell entirely. There is no safe way to use os.system() with user-controlled input; the function should be replaced with subprocess.run() using a list of arguments.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Arbitrary Command Execution

An attacker who controls any part of the string passed to os.system() can execute arbitrary commands on the server. Shell metacharacters like semicolons, pipes, backticks, and $() let attackers chain additional commands after the intended one, regardless of what comes before or after their input in the string.

2

Full Server Compromise

Commands injected through os.system() run as the Django application user. In containerized environments this is typically a low-privilege user, but in misconfigured deployments it may be the web server user or even root. In either case, an attacker can read application secrets, modify files, install backdoors, or pivot to other internal network services.

3

Data Exfiltration via DNS or HTTP

Even in restricted environments, attackers can use injected commands to exfiltrate data through DNS lookups, outbound HTTP requests, or writing to files served by the web application itself. Command injection in a restricted container can still lead to credential theft and lateral movement.

4

Reverse Shell Establishment

Attackers commonly use command injection to establish a reverse shell connection back to attacker-controlled infrastructure, providing persistent interactive access to the server even after the original vulnerability is patched.

How to Fix

Recommended remediation steps

  • 1Replace all os.system() calls with subprocess.run() using a list of arguments, which avoids shell interpretation entirely.
  • 2Never use shell=True in subprocess calls when any argument originates from user input, as this reintroduces shell injection risk.
  • 3Validate user input against strict allowlists (e.g., regex for hostname format, explicit set membership for command names) before passing to subprocess.
  • 4Consider whether the functionality requiring shell commands can be achieved with Python standard library functions that don't invoke a shell at all.
  • 5Run Django applications under a dedicated low-privilege user account and container security profiles (seccomp, AppArmor) to limit the impact of any successful injection.

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("os.system") with the tainted value tracked via .tracks(0) (the command string argument). Sanitizers include shlex.quote() for shell-escaped strings and explicit allowlist membership checks. The analysis follows taint across file and module boundaries.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-78 ranked #5 in 2023 Most Dangerous Software Weaknesses
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; SI-3: Malicious Code Protection
SANS Top 25
Improper Neutralization of Special Elements used in an OS Command

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Django Command Injection via os.system()

os.system() always passes its argument to the system shell for interpretation, which means shell metacharacters in user input will always be interpreted. subprocess.run(['command', 'arg']) with a list bypasses the shell entirely -- the list items are passed directly to execve() as argv, so metacharacters are treated as literal characters, not shell syntax.
shlex.quote() makes a single argument safe for inclusion in a shell command string, but it is still preferable to use subprocess with a list. If shlex.quote() is misapplied (e.g., applied to only part of the command, or to the entire command string rather than individual arguments), it may not protect against injection. The safest approach is subprocess with a list and no shell=True.
If a custom Django management command calls os.system() with user-influenced data (e.g., from command arguments that originated in a web request stored in the database), the rule will detect it if the taint flow is traceable. Management commands that process database values should also be audited for injection since stored XSS and second-order injection can originate from attacker-controlled database records.
Use an explicit allowlist: define a dictionary mapping safe command names to the actual subprocess list arguments. Validate that the user's input matches a key in the dictionary, then execute only the corresponding pre-defined command. Never construct the command list from user input, even for allowlisted command names.
Use subprocess with a pre-built list where the filename argument is validated with a strict regex (alphanumeric plus safe characters only) and prefixed with '--' to prevent it from being interpreted as a flag. For common file operations like image conversion or document processing, use Python libraries (Pillow, python-docx) instead of shelling out to system tools.
CWE-78 is #5 in the 2023 CWE Top 25. OWASP Top 10 A03:2021 covers injection broadly. PCI DSS v4.0 Requirement 6.2.4 requires protection against injection attacks. NIST SP 800-53 SI-10 requires input validation. Any SOC 2, ISO 27001, or FedRAMP assessment will include command injection in penetration testing scope.

New feature

Get these findings posted directly on your GitHub pull requests

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

See how it works