python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Detected subprocess function '$FUNC' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Run Locally

Run in CI

Defintion

rules:
  - id: dangerous-subprocess-use-audit
    pattern-either:
      - patterns:
          - pattern-not: subprocess.$FUNC("...", ...)
          - pattern-not: subprocess.$FUNC(["...",...], ...)
          - pattern-not: subprocess.$FUNC(("...",...), ...)
          - pattern-not:
              patterns:
                - pattern-not-inside: |
                    $ARR = ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", ...]
                    ...
                - pattern-inside: |
                    $ARR = [...]
                    ...
                - pattern-either:
                    - pattern: subprocess.$FUNC(*$ARR, ...)
                    - pattern: subprocess.$FUNC([*$ARR, ...])
          - pattern-not: subprocess.CalledProcessError(...)
          - pattern-not: subprocess.SubprocessError(...)
          - pattern: subprocess.$FUNC(...)
      - patterns:
          - pattern: subprocess.$FUNC("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",...)
          - pattern-not: subprocess.$FUNC("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c","...",...)
      - patterns:
          - pattern-either:
              - pattern: subprocess.$FUNC(["=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",...],...)
              - pattern: subprocess.$FUNC(("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",...),...)
          - pattern-not: subprocess.$FUNC(["=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c","...",...],...)
          - pattern-not: subprocess.$FUNC(("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c","...",...),...)
      - patterns:
          - pattern: subprocess.$FUNC("=~/(python)/",...)
          - pattern-not: subprocess.$FUNC("=~/(python)/","...",...)
      - patterns:
          - pattern-either:
              - pattern: subprocess.$FUNC(["=~/(python)/",...],...)
              - pattern: subprocess.$FUNC(("=~/(python)/",...),...)
          - pattern-not: subprocess.$FUNC(["=~/(python)/","...",...],...)
          - pattern-not: subprocess.$FUNC(("=~/(python)/","...",...),...)
    message: Detected subprocess function '$FUNC' without a static string. If this
      data can be controlled by a malicious actor, it may be an instance of
      command injection. Audit the use of this call to ensure it is not
      controllable by an external resource. You may consider using
      'shlex.escape()'.
    metadata:
      owasp:
        - A01:2017 - Injection
        - A03:2021 - Injection
      cwe:
        - "CWE-78: Improper Neutralization of Special Elements used in an OS
          Command ('OS Command Injection')"
      asvs:
        section: "V5: Validation, Sanitization and Encoding Verification Requirements"
        control_id: 5.3.8 OS Command Injection
        control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements
        version: "4"
      references:
        - https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess
        - https://docs.python.org/3/library/subprocess.html
        - https://docs.python.org/3/library/shlex.html
        - https://semgrep.dev/docs/cheat-sheets/python-command-injection/
      category: security
      technology:
        - python
      confidence: LOW
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - audit
      likelihood: LOW
      impact: HIGH
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Command Injection
    languages:
      - python
    severity: ERROR

Examples

dangerous-subprocess-use-audit.py

# cf. https://github.com/returntocorp/semgrep/blob/develop/docs/writing_rules/examples.md#auditing-dangerous-function-use

import subprocess
import sys

# ok:dangerous-subprocess-use-audit
subprocess.call("echo 'hello'")

# ok:dangerous-subprocess-use-audit
subprocess.call(["echo", "a", ";", "rm", "-rf", "/"])

# ok:dangerous-subprocess-use-audit
subprocess.call(("echo", "a", ";", "rm", "-rf", "/"))

# ok:dangerous-subprocess-use-audit
raise subprocess.CalledProcessError("{}".format("foo"))

# ok:dangerous-subprocess-use-audit
raise subprocess.SubprocessError("{}".format("foo"))

# ruleid:dangerous-subprocess-use-audit
subprocess.call("grep -R {} .".format(sys.argv[1]))

def foobar(user_input):
  cmd = user_input.split()
  # ruleid:dangerous-subprocess-use-audit
  subprocess.call([cmd[0], cmd[1], "some", "args"])

# ruleid:dangerous-subprocess-use-audit
subprocess.call("grep -R {} .".format(sys.argv[1]), shell=True)

# ruleid:dangerous-subprocess-use-audit
subprocess.call("grep -R {} .".format(sys.argv[1]), shell=True, cwd="/home/user")

# ruleid:dangerous-subprocess-use-audit
subprocess.run("grep -R {} .".format(sys.argv[1]), shell=True)

# ruleid:dangerous-subprocess-use-audit
subprocess.run(["bash", "-c", sys.argv[1]], shell=True)

# ok:dangerous-subprocess-use-audit
subprocess.call(["echo", "a", ";", "rm", "-rf", "/"])

cmd_cmd = ["sh", "-c"]
# ruleid:dangerous-subprocess-use-audit
subprocess.call([*cmd_cmd, "rm", "-rf", "/"])

echo_cmd = ["echo", "a", ";"]
# ok:dangerous-subprocess-use-audit
subprocess.call([*echo_cmd, "rm", "-rf", "/"])

def vuln_payload(payload: str) -> None:
  with tempfile.TemporaryDirectory() as directory:
    python_file = Path(directory) / "hello_world.py"
    python_file.write_text(textwrap.dedent("""
        print("What is your name?")
        name = input()
        print("Hello " + name)
    """))
    # ruleid:dangerous-subprocess-use-audit
    program = subprocess.Popen(['python2', str(python_file)], stdin=subprocess.PIPE, text=True)
    program.communicate(input=payload, timeout=1)