Dangerous os.exec*() Call

HIGH

os.exec*() replaces the current process image with a new program, enabling arbitrary program execution when arguments are untrusted.

Rule Information

Language
Python
Category
Python Core
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonos-execcommand-injectionprocess-replacementCWE-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-011 --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
22

About This Rule

Understanding the vulnerability and how it is detected

Python's os.execl(), os.execle(), os.execlp(), os.execlpe(), os.execv(), os.execve(), os.execvp(), and os.execvpe() functions replace the currently running Python process with a new program. Unlike os.system() or subprocess, there is no return from these calls; the Python runtime is completely replaced by the new process.

When the executable path or any argument passed to os.exec*() is derived from untrusted input, an attacker can cause the process to execute arbitrary programs with the current process's privileges, environment, file descriptors, and any capabilities it holds.

The os.exec*() variants that take a list of arguments (execv, execve, execvp, execvpe) do not invoke a shell and are safer than os.system() when arguments are properly separated. However, if the executable path itself is attacker-controlled, any program on the system can be run.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Arbitrary Program Execution

An attacker who controls the executable path can run any program accessible to the process, including /bin/sh, python3, nc (netcat), or any other tool available on the system, with the current process's effective UID, capabilities, and environment.

2

Process Replacement Without Return

os.exec*() does not return; the Python runtime is completely replaced. Any cleanup code, exception handlers, or security checks that would run after the call are bypassed. The replaced process inherits open file descriptors and environment variables containing secrets.

3

Environment Variable Injection

The execve and execvpe variants accept an explicit environment dictionary. If this dictionary is constructed from user input, an attacker can set LD_PRELOAD, PATH, or other sensitive environment variables to influence how the new process loads shared libraries and resolves commands.

4

Privilege Maintenance

Unlike spawning a subprocess, os.exec*() maintains the same PID, credentials, and resource limits. On systems where the Python process has elevated privileges (setuid, capabilities), the replacement program inherits those privileges.

How to Fix

Recommended remediation steps

  • 1Replace os.exec*() with subprocess.run() using a list of arguments to spawn a child process that can be monitored and does not replace the Python runtime.
  • 2If process replacement is genuinely required, use hardcoded absolute paths for the executable and validate all argument values against strict allowlists.
  • 3Never derive the executable path from user input; always use a hardcoded absolute path or a mapping from an allowlisted name to its absolute path.
  • 4Use subprocess with a timeout, stdout/stderr capture, and check=True to handle errors gracefully rather than silently replacing the process.
  • 5Ensure the current process runs with minimum required privileges to limit what an attacker can do by injecting an executable path.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule detects calls to os.execl(), os.execle(), os.execlp(), os.execlpe(), os.execv(), os.execve(), os.execvp(), and os.execvpe() from the Python os module. All variants are flagged since they all ultimately execute an external program and can be exploited when arguments are attacker-controlled.

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.exec*() Call

os.system() forks a child process, runs the shell command, and returns the exit code to the Python process which continues running. os.execv() replaces the current process entirely and never returns. Both can execute arbitrary programs when given untrusted arguments, but os.exec*() is more destructive since it eliminates any cleanup code.
The list-based variants do not invoke a shell, so they are not vulnerable to shell metacharacter injection. However, they are still dangerous when the executable path itself is attacker-controlled. An attacker can point the path to /bin/sh to get a shell. Validate both the executable path and all arguments.
os.exec*() is used in low-level system programming such as implementing custom process supervisors, security wrappers that need to replace themselves with a restricted process, or setuid helpers that drop privileges and exec a target program. These use cases should always use hardcoded executable paths and validated argument lists.
Yes. The new process inherits all open file descriptors (including sockets, pipes, and files containing credentials) and all environment variables unless explicitly replaced using execve/execvpe with a custom environment dictionary. Close sensitive file descriptors before calling exec*() using the close_fds parameter in subprocess or explicit os.close() calls.
An attacker-controlled executable receives all open file descriptors of the Python process, which may include authenticated database connections, open SSL connections, file handles to credential files, and IPC sockets. This can lead to credential theft and session hijacking beyond the immediate command execution risk.
Code Pathfinder's taint analysis traces data flow from HTTP request parameters, os.environ, file reads, and other external sources to os.exec*() call sites. Run the analysis with inter-procedural mode enabled to catch cases where the executable path is constructed in a helper function and passed to exec*() in another module.

New feature

Get these findings posted directly on your GitHub pull requests

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

See how it works