This product is not supported for your selected Datadog site. ().
このページは日本語には対応しておりません。随時翻訳に取り組んでいます。
翻訳に関してご質問やご意見ございましたら、お気軽にご連絡ください

Metadata

ID: python-flask/command-injection

Language: Python

Severity: Error

Category: Security

CWE: 78

Description

This rule identifies potential OS Command Injection vulnerabilities in Python code. These vulnerabilities occur when an application constructs shell commands by incorporating user-supplied or otherwise untrusted data directly into command strings without proper sanitization. An attacker can exploit this by crafting input that includes malicious shell commands, which are then executed on the server with the privileges of the running application. This can lead to unauthorized server access, data breaches, arbitrary code execution, or denial of service.

The rule specifically looks for:

  1. Calls to subprocess module functions (like run, call, Popen, etc.) where the command argument is built using string formatting (f-strings, .format(), % operator) or concatenation with potentially unsanitized variables, especially when shell=True might be implied or used.
  2. Assignments to variables (often named cmd, command, args, etc.) where the string value is constructed using these unsafe methods, and these variables are likely intended for command execution.

How to Remediate

To prevent OS Command Injection vulnerabilities when constructing and executing shell commands in Python:

  1. Prefer List Arguments with shell=False: When using subprocess functions (e.g., subprocess.run(), subprocess.call()), pass the command and its arguments as a list of strings. Ensure shell=False is used (this is the default for subprocess.run()). This method treats each item in the list as a literal argument and does not invoke a shell to interpret the command string, significantly reducing the risk of injection.

    import subprocess
    
    user_filename = "some_file.txt" # Example of an argument, potentially from user input
    
    # Compliant: command and arguments as a list, shell=False (default)
    try:
        result = subprocess.run(["cat", user_filename], capture_output=True, text=True, check=True)
        print(result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"Error executing command: {e}")
    except FileNotFoundError:
        print("Error: Command not found.")
    
  2. Use shlex.quote() for Shell Execution: If you absolutely must use shell=True or need to construct a single command string that will be interpreted by the shell, sanitize any dynamic parts of the command string (especially those derived from external input) using shlex.quote(). This function escapes characters that have special meaning to the shell, making the input safe.

    import subprocess
    import shlex
    
    user_input = "some value; malicious_command" # Example of user input
    safe_argument = shlex.quote(user_input)
    
    # Compliant: user input is quoted before being included in a command string for shell=True
    try:
        result = subprocess.run(f"echo {safe_argument}", shell=True, capture_output=True, text=True, check=True)
        print(result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"Error executing command: {e}")
    
  3. Avoid Direct String Concatenation/Formatting for Commands: Do not build command strings by directly concatenating or formatting them with untrusted input if the command will be processed by a shell.

    # Non-compliant example:
    # user_directory = request.args.get("dir")
    # command = "ls -l " + user_directory 
    # subprocess.run(command, shell=True) # Vulnerable
    
    # Non-compliant example:
    # search_term = request.args.get("term")
    # command = f"grep '{search_term}' /var/log/app.log"
    # subprocess.run(command, shell=True) # Vulnerable
    

    Instead, use the list-based approach (Remediation #1) or shlex.quote() (Remediation #2).

  4. Validate and Sanitize All External Input: Beyond specific command construction, rigorously validate and sanitize any external input that influences program behavior, including what commands are run or what arguments are passed. Use allowlists for permissible inputs where possible.

  5. Principle of Least Privilege: Run your application with the minimum privileges necessary. This will not prevent the injection itself but can limit the potential damage if such a vulnerability is exploited.

Non-Compliant Code Examples

import subprocess
import urllib.parse
from flask import request

def vulnerable_example1():
    param = request.headers.get('user_input', '')
    param = urllib.parse.unquote(param)
    command = f"echo {param}"
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    return result.stdout

def vulnerable_example2():
    user_input = get_user_input()
    cmd = "ls " + user_input
    subprocess.call(cmd, shell=True)

def vulnerable_example3():
    filename = request.args.get('file')
    args = ["sh", "-c", "cat " + filename]
    subprocess.run(args)

def vulnerable_example4():
    search = input("Enter search term: ")
    subprocess.check_output("grep '{}'".format(search), shell=True)

Compliant Code Examples

import subprocess
import shlex

def safe_example1():
    # Using list arguments without concatenation
    result = subprocess.run(["echo", "hello"], capture_output=True, text=True)
    return result.stdout

def safe_example2():
    # Using shlex.quote for proper escaping
    user_input = get_user_input()
    safe_input = shlex.quote(user_input)
    subprocess.run(f"echo {safe_input}", shell=True)

def safe_example3():
    # Parameterized approach
    filename = "hardcoded.txt"
    subprocess.run(["cat", filename])

def safe_example4():
    # Static command without user input
    command = "ls -la /tmp"
    subprocess.run(command, shell=True)

# annotation_count: 0
https://static.datadoghq.com/static/images/logos/github_avatar.svg https://static.datadoghq.com/static/images/logos/vscode_avatar.svg jetbrains

シームレスな統合。 Datadog Code Security をお試しください