python.lang.security.audit.dangerous-system-call-tainted-env-args.dangerous-system-call-tainted-env-args

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Found user-controlled data used in a system call. This could allow a malicious actor to execute commands. Use the 'subprocess' module instead, which is easier to use without accidentally exposing a command injection vulnerability.

Run Locally

Run in CI

Defintion

rules:
  - id: dangerous-system-call-tainted-env-args
    mode: taint
    options:
      symbolic_propagation: true
    pattern-sources:
      - patterns:
          - pattern-either:
              - patterns:
                  - pattern-either:
                      - pattern: os.environ
                      - pattern: os.environ.get('$FOO', ...)
                      - pattern: os.environb
                      - pattern: os.environb.get('$FOO', ...)
                      - pattern: os.getenv('$ANYTHING', ...)
                      - pattern: os.getenvb('$ANYTHING', ...)
              - patterns:
                  - pattern-either:
                      - patterns:
                          - pattern-either:
                              - pattern: sys.argv
                              - pattern: sys.orig_argv
                      - patterns:
                          - pattern-inside: |
                              $PARSER = argparse.ArgumentParser(...)
                              ...
                          - pattern-inside: |
                              $ARGS = $PARSER.parse_args()
                          - pattern: <... $ARGS ...>
                      - patterns:
                          - pattern-inside: |
                              $PARSER = optparse.OptionParser(...)
                              ...
                          - pattern-inside: |
                              $ARGS = $PARSER.parse_args()
                          - pattern: <... $ARGS ...>
                      - patterns:
                          - pattern-either:
                              - pattern-inside: |
                                  $OPTS, $ARGS = getopt.getopt(...)
                                  ...
                              - pattern-inside: |
                                  $OPTS, $ARGS = getopt.gnu_getopt(...)
                                  ...
                          - pattern-either:
                              - patterns:
                                  - pattern-inside: |
                                      for $O, $A in $OPTS:
                                        ...
                                  - pattern: $A
                              - pattern: $ARGS
    pattern-sinks:
      - patterns:
          - pattern-not: os.$W("...", ...)
          - pattern-either:
              - pattern: os.system(...)
              - pattern: |
                  $X = __import__("os")
                  ...
                  $X.system(...)
              - pattern: |
                  $X = __import__("os")
                  ...
                  getattr($X, "system")(...)
              - pattern: |
                  $X = getattr(os, "system")
                  ...
                  $X(...)
              - pattern: |
                  $X = __import__("os")
                  ...
                  $Y = getattr($X, "system")
                  ...
                  $Y(...)
              - pattern: os.popen(...)
              - pattern: os.popen2(...)
              - pattern: os.popen3(...)
              - pattern: os.popen4(...)
    message: Found user-controlled data used in a system call. This could allow a
      malicious actor to execute commands. Use the 'subprocess' module instead,
      which is easier to use without accidentally exposing a command injection
      vulnerability.
    metadata:
      source-rule-url: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html
      cwe:
        - "CWE-78: Improper Neutralization of Special Elements used in an OS
          Command ('OS Command Injection')"
      owasp:
        - A01:2017 - Injection
        - A03:2021 - Injection
      references:
        - https://semgrep.dev/docs/cheat-sheets/python-command-injection/
      asvs:
        section: "V5: Validation, Sanitization and Encoding Verification Requirements"
        control_id: 5.2.4 Dyanmic Code Execution Features
        control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v52-sanitization-and-sandboxing-requirements
        version: "4"
      category: security
      technology:
        - python
      confidence: MEDIUM
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - vuln
      likelihood: MEDIUM
      impact: HIGH
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Command Injection
    languages:
      - python
    severity: ERROR

Examples

dangerous-system-call-tainted-env-args.py

import os

# ok:dangerous-system-call-tainted-env-args
os.system("ls -al")

# ok:dangerous-system-call-tainted-env-args
os.popen("cat contents.txt")

from somewhere import something

# fn:dangerous-system-call-tainted-env-args
os.system(something())

# fn:dangerous-system-call-tainted-env-args
os.popen(something())

# fn:dangerous-system-call-tainted-env-args
os.popen2(something())


# Environment true positives
def env1():
    envvar1 = os.environ["envvar"]

    # ruleid:dangerous-system-call-tainted-env-args
    os.system(envvar1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(envvar1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(envvar1)

    envvar2 = os.environ.get("envvar")

    # ruleid:dangerous-system-call-tainted-env-args
    os.system(envvar2)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(envvar2)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(envvar2)

    envvar3 = os.getenv("envvar")

    # ruleid:dangerous-system-call-tainted-env-args
    os.system(envvar3)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(envvar3)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(envvar3)


# Cmd line args
import argparse


def args1():
    parser = argparse.ArgumentParser(description="Oops!")
    parser.add_argument("arg1", type=str)
    args = parser.parse_args()
    arg1 = args.arg1

    # ruleid:dangerous-system-call-tainted-env-args
    os.system(arg1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(arg1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(arg1)


import optparse


def args2():
    parser = optparse.OptionParser()
    parser.add_option(
        "-f", "--file", dest="filename", help="write report to FILE", metavar="FILE"
    )
    (opts, args) = parser.parse_args()

    opt1 = opts.opt1
    # ruleid:dangerous-system-call-tainted-env-args
    os.system(opt1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(opt1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(opt1)

    arg1 = args.arg1
    # ruleid:dangerous-system-call-tainted-env-args
    os.system(arg1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(arg1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(arg1)


import getopt
import sys


def args3():
    opts, args = getopt.getopt(
        sys.argv[1:],
        "hl:p:",
        ["help", "local_path", "parameter"],
    )

    for opt, arg in opts:
        # ruleid:dangerous-system-call-tainted-env-args
        os.system(arg)
        # ruleid:dangerous-system-call-tainted-env-args
        os.popen(arg)
        # ruleid:dangerous-system-call-tainted-env-args
        os.popen2(arg)

        # ok:dangerous-system-call-tainted-env-args
        os.system(opt)
        # ok:dangerous-system-call-tainted-env-args
        os.popen(opt)
        # ok:dangerous-system-call-tainted-env-args
        os.popen2(opt)

    for arg in args:
        # ruleid:dangerous-system-call-tainted-env-args
        os.system(arg)
        # ruleid:dangerous-system-call-tainted-env-args
        os.popen(arg)
        # ruleid:dangerous-system-call-tainted-env-args
        os.popen2(arg)


def args4():
    arg1 = sys.argv[1]
    # ruleid:dangerous-system-call-tainted-env-args
    os.system(arg1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(arg1)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(arg1)

    arg2 = sys.argv[2]
    # ruleid:dangerous-system-call-tainted-env-args
    os.system(arg2)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen(arg2)
    # ruleid:dangerous-system-call-tainted-env-args
    os.popen2(arg2)


def open_url(url, wait=False, locate=False):
    import subprocess

    if WIN:
        url = url.replace('"', "")
        wait = "/WAIT" if wait else ""
        args = f'start {wait} "" "{url}"'
        return os.system(args)