Dangerous os.system() or os.popen() Call

HIGH

os.system() and os.popen() execute shell commands via /bin/sh, enabling command injection when arguments contain untrusted input.

Rule Information

Language
Python
Category
Python Core
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonos-systemos-popencommand-injectionshell-injectionCWE-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-LANG-SEC-010 --project .
1
2
3
4
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

About This Rule

Understanding the vulnerability and how it is detected

Python's os.system() and os.popen() functions execute a shell command string by invoking the system shell (/bin/sh on Unix, cmd.exe on Windows). Because the command is passed through a shell interpreter, special characters such as semicolons, pipes, backticks, dollar signs, and redirections are interpreted as shell metacharacters.

When any part of the command string is derived from untrusted user input, an attacker can inject additional commands using shell metacharacters. For example, user input of "file.txt; rm -rf /" would cause os.system("cat file.txt; rm -rf /") to execute both commands.

The recommended replacement is subprocess.run() with a list of arguments (not a shell string), which bypasses the shell entirely and passes arguments directly to the OS exec() syscall. subprocess.run(["cat", filename]) is safe regardless of what filename contains.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

OS Command Injection

Shell metacharacters in user-supplied input allow injecting additional commands. An attacker can chain arbitrary commands using semicolons, pipes, or newlines, executing them with the privileges of the Python process.

2

Data Exfiltration

Injected commands can read sensitive files (credentials, private keys, configuration), encode them, and send them to an attacker-controlled endpoint using curl, wget, or other available tools on the system.

3

Persistent Backdoor Installation

Command injection can write cron jobs, SSH authorized_keys, or systemd unit files that survive process restarts and provide persistent access to the attacker even after the vulnerability is patched.

4

Lateral Movement

On cloud environments or internal networks, injected commands can access metadata services for IAM credential theft, scan internal networks, and pivot to other systems using the compromised host's network access.

How to Fix

Recommended remediation steps

  • 1Replace os.system() and os.popen() with subprocess.run() using a list of arguments, which bypasses the shell entirely.
  • 2Never construct shell command strings by concatenating or interpolating user input, even if the input appears to be validated.
  • 3If a shell pipeline is genuinely required, validate all user-supplied values against a strict allowlist before including them.
  • 4Set a timeout on subprocess.run() to prevent resource exhaustion from long-running injected commands.
  • 5Run the Python process with the minimum OS privileges required so that injected commands cannot escalate to root or access sensitive paths.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule detects calls to os.system(), os.popen(), os.popen2(), os.popen3(), and os.popen4() from the Python os module. All call sites are flagged since these functions always invoke the system shell and are vulnerable to injection whenever the command string includes non-literal content.

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
NIST SP 800-53
SI-10: Information Input Validation
PCI DSS v4.0
Requirement 6.2.4 - Protect web-facing applications against injection attacks

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Dangerous os.system() or os.popen() Call

Regex validation reduces but does not eliminate the risk. Shell metacharacter sets vary by shell and locale, and regex patterns often miss edge cases like null bytes, Unicode lookalikes, or environment variable expansion. The safe approach is to use subprocess.run() with a list so the shell is never invoked and metacharacter interpretation is impossible.
Both invoke the system shell and are equally vulnerable to command injection. The safe alternative is subprocess.run() with a list of arguments and shell=False (the default), which passes the arguments directly to the OS exec() syscall without shell interpretation.
Yes. os.popen() also passes the command to the shell and is vulnerable to the same injection attacks. Additionally, os.popen() was deprecated in Python 2.6 in favor of the subprocess module. There is no reason to use os.popen() in modern Python code.
Use Python built-in equivalents: the glob module for file globbing, subprocess.PIPE for connecting processes, and pathlib for path manipulation. If a shell pipeline is unavoidable, use subprocess.run() with shell=True but ensure every element of the command string is either a hardcoded literal or validated against a strict allowlist.
Yes. All os.system() call sites are flagged as a security audit point. Hardcoded strings are lower risk but may still be problematic if the hardcoded command includes values that could be influenced by environment variables, file contents, or other runtime state.
Use a timeout appropriate to the expected execution time of the command, typically a few seconds for file operations and up to a minute for external tools. Setting timeout=30 as a default is a reasonable starting point. Always handle subprocess.TimeoutExpired to terminate the child process cleanly.

New feature

Get these findings posted directly on your GitHub pull requests

The Dangerous os.system() or os.popen() Call rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works