> ## Documentation Index
> Fetch the complete documentation index at: https://docs.semgrep.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Prevent Command Injection for Python

This is a command injection prevention cheat sheet by Semgrep, Inc. It contains code patterns of potential ways to run an OS command in an application. Instead of scrutinizing code for exploitable vulnerabilities, the recommendations in this cheat sheet pave a safe road for developers that mitigate the possibility of command injection in your code. By following these recommendations, you can be reasonably sure your code is free of command injection.

Learn more about [Command Injection](/learn/vulnerabilities/command-injection) vulnerability concepts.

### Check your project using Semgrep

```bash theme={null}
semgrep --config auto .
```

## 1. Running an OS command

### 1.A. Using the subprocess module

The `subprocess` module allows you to start new processes, connect to their input/output/error pipes, and obtain their return codes. Methods such as `Popen`, `run`, `call`, `check_call`, `check_output` are intended for running commands provided as an argument. Allowing user input in a command that is passed as an argument to one of these methods can create an opportunity for a command injection vulnerability.

Example:

```python theme={null}
import subprocess
import sys

# Vulnerable
user_input = "foo && cat /etc/passwd" # value supplied by user
subprocess.call("grep -R {} .".format(user_input), shell=True)

# Vulnerable
user_input = "cat /etc/passwd" # value supplied by user
subprocess.run(["bash", "-c", user_input], shell=True)

# Not vulnerable
user_input = "cat /etc/passwd" # value supplied by user
subprocess.Popen(['ls', '-l', user_input])

# Not vulnerable
subprocess.check_output('ls -l dir/')
```

#### References

* [**subprocess** module documentation](https://docs.python.org/3/library/subprocess.html)
* [**shlex.split** documentation](https://docs.python.org/3/library/shlex.html#shlex.split)
* [**shlex.quote** documentation](https://docs.python.org/3/library/shlex.html#shlex.quote)
* [CVE-2020-7698: Gerapy Command Injection](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7698)
* [CVE-2020-11981: Apache Airflow Command Injection](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11981)

#### Mitigation

Do not let a user input into `subprocess` methods. Alternatively:

* Always try to use internal Python API (if it exists) instead of running an OS command. In other words, use internal language features instead of invoking commands that can be exploited.
* Don't pass user-controlled input.
* If it is not possible, use an array with a sequence of program arguments instead of a single string.
* Use `shlex.split` to correctly parse a command string into an array and `shlex.quote` to correctly sanitize input as a command-line parameter.
* Do not include command arguments in a command string, use parameterization instead. For example:<br />
  Use:
  ```python theme={null}
  subprocess.run(["/path/to/myCommand", "myArg1", input_value])
  ```
  Instead of:
  ```python theme={null}
  subprocess.run(["bash", "-c", "myCommand myArg1 " + input_value])
  ```
* If it is not possible, strip everything except alphanumeric characters from an input provided for the command string and arguments.

#### Semgrep rule

[`python.lang.security.audit.dangerous-subprocess-use`](https://semgrep.dev/r/python.lang.security.audit.dangerous-subprocess-use)

### 1.B. shell=True

Functions from the `subprocess` module have the `shell` argument for specifying if the command should be executed through the shell.
Using `shell=True` is dangerous because it propagates current shell settings and variables.
This means that variables, glob patterns, and other special shell features in the command string are processed before the command is run,
making it much easier for a malicious actor to execute commands. The `subprocess` module allows you to start new processes, connect to their input/output/error pipes, and obtain their return codes. Methods such as `Popen`, `run`, `call`, `check_call`, `check_output` are intended for running commands provided as an argument ('args'). Allowing user input in a command that is passed as an argument to one of these methods can create an opportunity for a command injection vulnerability.

Example:

```python theme={null}
# prints home directory
subprocess.call('echo $HOME', shell=True)

# throws an error
subprocess.call('echo $HOME', shell=False)
```

#### References

* [StackOverflow thread about **shell=True**](https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess)
* [Explanation of **shell** argument from documentation](https://docs.python.org/3/library/subprocess.html#frequently-used-arguments)

#### Mitigation

Avoid using `shell=True`. Alternatively, use `shell=False` instead.

#### Semgrep rule

[`python.lang.security.audit.subprocess-shell-true`](https://semgrep.dev/r/python.lang.security.audit.subprocess-shell-true)

### 1.C. Using os module to execute commands

The `os` module provides a portable way of using operating system dependent functionality. Methods such as `system`, `popen` and deprecated `popen2`, `popen3` and `popen4` are intended for running commands provided as a string. Letting user supplied data into a command that is passed as an argument to one of these methods can create an opportunity for a command injection vulnerability.

Example:

```python theme={null}
import os

# Vulnerable
user_input = "foo && cat /etc/passwd" # value supplied by user
os.system("grep -R {} .".format(user_input))

# Vulnerable
user_input = "foo && cat /etc/passwd" # value supplied by user
os.popen("ls -l " + user_input)
```

#### References

* [**os** module documentation](https://docs.python.org/3/library/os.html)

#### Mitigation

Do not let a user input into `os` methods. Alternatively:

* Always try to use an internal Python API (if it exists) instead of running an OS command.
* Consider using `subprocess` functions with array of program arguments.
* Don’t pass user-controlled input.
* If it is not possible, then don’t let running arbitrary commands. Use an allowlist for inputs.

#### Semgrep rule

[`python.lang.security.audit.dangerous-system-call`](https://semgrep.dev/r/python.lang.security.audit.dangerous-system-call)

### 1.D. Using os module to spawn a process

The `os` module allows executing the program path in a new process. Variations of spawn method including `spawnl`, `spawnle`, `spawnlp`, `spawnlpe`, `spawnv`, `spawnve`, `spawnvp`, `spawnvpe`, `posix_spawn` and `posix_spawnp` are intended for spawning a process with a program passed as a string argument. Allowing spawning of arbitrary programs or running shell processes with arbitrary arguments may result in a command injection vulnerability.

Example:

```python theme={null}
import os

# Vulnerable
user_input = "/foo/bar" # value supplied by user
os.spawnlpe(os.P_WAIT, user_input, ["-a"], os.environ)

# Vulnerable
user_input = "cat /etc/passwd" # value supplied by user
os.spawnve(os.P_WAIT, "/bin/bash", ["-c", user_input], os.environ)
```

#### References

* [**os** module documentation](https://docs.python.org/3/library/os.html)

#### Mitigation

Do not let a user input into `spawn` methods. Alternatively:

* Always try to use an internal Python API (if it exists) instead of running an OS command.
* Don’t pass user-controlled input.
* Use `shlex.split` to correctly parse a command string into an array and `shlex.quote` to correctly sanitize input as a command-line parameter.
* Do not include command arguments in a command string, use parameterization instead. For example:<br />
  Use:
  ```python theme={null}
  os.spawnve(os.P_WAIT, "/path/to/myCommand", ["myArg1", input_value], os.environ)
  ```
  Instead of:
  ```python theme={null}
  os.spawnve(os.P_WAIT, "/bin/bash", ["-c", "myCommand myArg1 " + input_value], os.environ)
  ```
* If it’s not possible to avoid, strip everything except alphanumeric characters from an input provided for the command string and arguments.

#### Semgrep rule

[`python.lang.security.audit.dangerous-spawn-process.dangerous-spawn-process`](https://semgrep.dev/r/python.lang.security.audit.dangerous-spawn-process.dangerous-spawn-process)

### 1.E. Replacing current process with exec

Execution methods of the `os` module are intended to execute a new program, replacing the current process. The available methods are `execl`, `execle`, `execlp`, `execlpe`, `execv`, `execve`, `execvp`, and `execvpe`. Allowing running of arbitrary programs or running shell processes with arbitrary arguments may result in a command injection vulnerability.

Example:

```python theme={null}
import os

# Vulnerable
user_input = "/evil/code" # value supplied by user
os.execl(user_input, '/foo/bar', '--do-smth')

# Vulnerable
user_input = "cat /etc/passwd" # value supplied by user
os.execve("/bin/bash", ["/bin/bash", "-c", user_input], os.environ)
```

#### References

* [**os** module documentation](https://docs.python.org/3/library/os.html)

#### Mitigation

Do not let a user input into `exec` methods. Alternatively:

* Always try to use internal an Python API (if it exists) instead of running an OS command.
* Don't pass user-controlled input.
* Use `shlex.split` to correctly parse a command string into an array and `shlex.quote` to correctly sanitize input as a command-line parameter.
* Do not include command arguments in a command string, use parameterization instead. For example:<br />
  Use:
  ```python theme={null}
  os.execve("/path/to/myCommand", ["myCommand", "myArg1", input_value], os.environ)
  ```
  Instead of:
  ```python theme={null}
  os.execve("/bin/bash", ["/bin/bash", "-c", "myCommand myArg1 " + input_value], os.environ)
  ```
* If it's not possible to avoid, strip everything except alphanumeric characters from an input provided for the command string and arguments.

#### Semgrep rule

[`python.lang.security.audit.dangerous-spawn-process.dangerous-spawn-process`](https://semgrep.dev/r/python.lang.security.audit.dangerous-spawn-process.dangerous-spawn-process)

### 1.F. Wildcard character in a system call that spawns a shell

Spawning a shell or executing a Unix shell command with a wildcard leads to normal shell expansion, which can have unintended consequences if there exist any non-standard file names. Consider a file named '-e sh script.sh' -- this will execute a script when 'rsync' is called.

Example:

```bash theme={null}
# directory example
[root@user public]# ls -al
total 20
drwxrwxr-x.  5 user   user   4096 Oct 28 17:04 .
drwx------. 22 user   user   4096 Oct 28 16:15 ..
drwxrwxr-x.  2 user   user   4096 Oct 28 17:04 DIR1
drwxrwxr-x.  2 user   user   4096 Oct 28 17:04 DIR2
drwxrwxr-x.  2 user   user   4096 Oct 28 17:04 DIR3
-rw-rw-r--.  1 user   user      0 Oct 28 17:03 file1.txt
-rw-rw-r--.  1 nobody nobody    0 Oct 28 16:38 -rf


# running Python code like this will use `-rf` as an argument for rm and force delete all directories
os.system("/bin/rm *")
```

#### References

* [Article about Unix wildcard poisoning tricks](https://www.exploit-db.com/exploits/33930)

#### Mitigation

Avoid wildcards in Unix shell commands.

#### Semgrep rule

[`python.lang.security.audit.system-wildcard-detected`](https://semgrep.dev/r/python.lang.security.audit.system-wildcard-detected)

### 1.G. Running shell commands asynchronously

The `asyncio.subprocess` is an async or await API to create and manage subprocesses. Such methods as `create_subprocess_shell` and Event Loop's `subprocess_shell` are intended for running shell commands provided as an argument 'cmd'. Allowing user input into a command that is passed as an argument to one of these methods can create an opportunity for a command injection vulnerability.

Example:

```python theme={null}
import asyncio

# Vulnerable
user_input = "cat /etc/passwd" # value supplied by user
loop = asyncio.new_event_loop()
# This is similar to the standard library subprocess.Popen class called with shell=True
loop.subprocess_shell(asyncio.SubprocessProtocol, user_input)

# Vulnerable
user_input = "cat /etc/passwd" # value supplied by user
asyncio.subprocess.create_subprocess_shell(user_input)
```

#### References

* [**asyncio subprocess** documentation](https://docs.python.org/3/library/asyncio-subprocess.html)
* [**Event Loop subprocess\_shell** documentation](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_shell)

#### Mitigation

Do not let a user input into `asyncio.subprocess` methods. Alternatively:

* Always try to use an internal Python API (if it exists) instead of running an OS command.
* Consider using `asyncio.subprocess` functions with array of program arguments (for example: `create_subprocess_exec`).
* Don't pass user-controlled input.
* If it's not possible, then don't let running arbitrary commands. Use an allowlist for inputs.

#### Semgrep rule

[`python.lang.security.audit.dangerous-asyncio-shell.dangerous-asyncio-shell`](https://semgrep.dev/r/python.lang.security.audit.dangerous-asyncio-shell.dangerous-asyncio-shell)

### 1.H. Creating subprocesses asynchronously

The `asyncio.subprocess` also allows asynchronous creation of subprocesses. Such methods as `create_subprocess_exec` and Event Loop's `subprocess_exec` are intended for creating a subprocess from one or more string arguments specified by args. Allowing user input into a command that is passed as an argument to one of these methods can create an opportunity for a command injection vulnerability.

Example:

```python theme={null}
import asyncio

# Vulnerable
user_input = "/evil/exe" # value supplied by user
loop = asyncio.new_event_loop()
loop.subprocess_exec(asyncio.SubprocessProtocol, [user_input, "--parameter"])

# Vulnerable
user_input = "cat /etc/passwd" # value supplied by user
asyncio.subprocess.create_subprocess_exec("bash", ["bash", "-c", user_input])

# Not vulnerable
user_input = "/evil/exe" # value supplied by user
loop = asyncio.new_event_loop()
loop.subprocess_exec(asyncio.SubprocessProtocol, ['ls', '-l', user_input])
```

#### References

* [asyncio subprocess documentation](https://docs.python.org/3/library/asyncio-subprocess.html)

#### Mitigation

Do not let a user input into `asyncio subprocess` methods. Alternatively:

* Always try to use an internal Python API (if it exists) instead of running an OS command.
* Don't pass user-controlled input.
* Use `shlex.split` to correctly parse a command string into an array and `shlex.quote` to correctly sanitize input as a command-line parameter.
* Do not include command arguments in a command string, use parameterization instead. For example:<br />
  Use:
  ```python theme={null}
  asyncio.subprocess.create_subprocess_exec("/path/to/myCommand", ["myCommand", "myArg1", input_value])
  ```
  Instead of:
  ```python theme={null}
  asyncio.subprocess.create_subprocess_exec("bash", ["bash", "-c", "myCommand myArg1 " + input_value])
  ```
* If it's not possible to avoid, strip everything except alphanumeric characters from an input provided for the command string and arguments.

#### Semgrep rule

[`python.lang.security.audit.dangerous-asyncio-exec.dangerous-asyncio-exec`](https://semgrep.dev/r/python.lang.security.audit.dangerous-asyncio-exec.dangerous-asyncio-exec)<br />
[`python.lang.security.audit.dangerous-asyncio-create-exec.dangerous-asyncio-create-exec`](https://semgrep.dev/r/python.lang.security.audit.dangerous-asyncio-create-exec.dangerous-asyncio-create-exec)
