python.flask.security.injection.subprocess-injection.subprocess-injection

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Detected user input entering a subprocess call unsafely. This could result in a command injection vulnerability. An attacker could use this vulnerability to execute arbitrary commands on the host, which allows them to download malware, scan sensitive data, or run any command they wish on the server. Do not let users choose the command to run. In general, prefer to use Python API versions of system commands. If you must use subprocess, use a dictionary to allowlist a set of commands.

Run Locally

Run in CI

Defintion

rules:
  - id: subprocess-injection
    languages:
      - python
    mode: taint
    options:
      symbolic_propagation: true
    pattern-sources:
      - pattern-either:
          - patterns:
              - pattern-either:
                  - pattern: flask.request.form.get(...)
                  - pattern: flask.request.form[...]
                  - pattern: flask.request.args.get(...)
                  - pattern: flask.request.args[...]
                  - pattern: flask.request.values.get(...)
                  - pattern: flask.request.values[...]
                  - pattern: flask.request.cookies.get(...)
                  - pattern: flask.request.cookies[...]
                  - pattern: flask.request.stream
                  - pattern: flask.request.headers.get(...)
                  - pattern: flask.request.headers[...]
                  - pattern: flask.request.data
                  - pattern: flask.request.full_path
                  - pattern: flask.request.url
                  - pattern: flask.request.json
                  - pattern: flask.request.get_json()
                  - pattern: flask.request.view_args.get(...)
                  - pattern: flask.request.view_args[...]
          - patterns:
              - pattern-inside: |
                  @$APP.route($ROUTE, ...)
                  def $FUNC(..., $ROUTEVAR, ...):
                    ...
              - focus-metavariable: $ROUTEVAR
    pattern-sinks:
      - patterns:
          - pattern-either:
              - patterns:
                  - pattern: subprocess.$FUNC(...)
                  - pattern-not: subprocess.$FUNC("...", ...)
                  - pattern-not: subprocess.$FUNC(["...", ...], ...)
                  - pattern-not-inside: |
                      $CMD = ["...", ...]
                      ...
                      subprocess.$FUNC($CMD, ...)
              - patterns:
                  - pattern: subprocess.$FUNC(["$SHELL", "-c", ...], ...)
                  - metavariable-regex:
                      metavariable: $SHELL
                      regex: ^(sh|bash|ksh|csh|tcsh|zsh)$
              - patterns:
                  - pattern: subprocess.$FUNC(["$INTERPRETER", ...], ...)
                  - metavariable-regex:
                      metavariable: $INTERPRETER
                      regex: ^(python|python\d)$
    pattern-sanitizers:
      - patterns:
          - pattern: $DICT[$KEY]
          - focus-metavariable: $KEY
    severity: ERROR
    message: Detected user input entering a `subprocess` call unsafely. This could
      result in a command injection vulnerability. An attacker could use this
      vulnerability to execute arbitrary commands on the host, which allows them
      to download malware, scan sensitive data, or run any command they wish on
      the server. Do not let users choose the command to run. In general, prefer
      to use Python API versions of system commands. If you must use subprocess,
      use a dictionary to allowlist a set of commands.
    metadata:
      category: security
      technology:
        - flask
      owasp:
        - A01:2017 - Injection
        - A03:2021 - Injection
      cwe:
        - "CWE-78: Improper Neutralization of Special Elements used in an OS
          Command ('OS Command Injection')"
      references:
        - https://semgrep.dev/docs/cheat-sheets/python-command-injection/
      confidence: HIGH
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - vuln
      likelihood: HIGH
      impact: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Command Injection

Examples

subprocess-injection.py

import subprocess
import sys
import flask

app = flask.Flask(__name__)

@app.route("a")
def a():
    ip = flask.request.args.get("ip")
    # ruleid:subprocess-injection
    subprocess.run("ping "+ ip)

@app.route("b")
def b():
    host = flask.request.headers["HOST"]
    # ruleid:subprocess-injection
    subprocess.run("echo {} > log".format(host))

@app.route("c/<ip>")
def c(ip):
    # ruleid:subprocess-injection
    subprocess.run("ping "+ ip)

@app.route("d/<cmd>/<ip>")
def d(cmd, ip):
    command = [cmd, ip]
    # ruleid:subprocess-injection
    subprocess.capture_output(command)

@app.route("e")
def e():
    event = flask.request.json
    cmd = event['id'].split()
    # ruleid:subprocess-injection
    subprocess.call([cmd[0], cmd[1], "some", "args"])

@app.route("f")
def f():
    event = flask.request.get_json()
    # ruleid:subprocess-injection
    subprocess.run(["bash", "-c", event['id']], shell=True)

@app.route("g")
def g():
    event = flask.request.json
    python_file = f"""
        print("What is your name?")
        name = input()
        print("Hello " + {event['id']})
    """
    # ruleid:subprocess-injection
    program = subprocess.Popen(['python2', python_file], stdin=subprocess.PIPE, text=True)
    program.communicate(input=payload, timeout=1)

@app.route("d_ok/<cmd>/<ip>")
def d_ok(cmd, ip):
    # ok:subprocess-injection
    subprocess.capture_output(["ping", cmd, ip])

@app.route("d_ok2/<ip>")
def d_ok2(ip):
    cmd = ["ping", ip]
    # ok:subprocess-injection
    subprocess.capture_output(cmd)

@app.route("e")
def e_ok():
    allowed = {'p': "ping"}

    event = flask.request.json
    cmd = event['id'].split()

    valid = allowed[cmd[0]]
    # ok:subprocess-injection
    subprocess.call([valid, "some", "args"])


@app.route("ok")
def ok():
    ip = flask.request.args.get("ip")
    subprocess.run(["ping", ip])

@app.route("ok2")
def ok2():
    ip = flask.request.args.get("ip")
    subprocess.run("echo 'nothing'")

@app.route("ok3")
def ok3():
    ip = flask.request.args.get("ip")
    subprocess.call(["echo", "a", ";", "rm", "-rf", "/"])