python.flask.security.injection.tainted-url-host.tainted-url-host

profile photo of semgrepsemgrep
Author
unknown
Download Count*

User data flows into the host portion of this manually-constructed URL. This could allow an attacker to send data to their own server, potentially exposing sensitive data such as cookies or authorization information sent with this request. They could also probe internal servers or other resources that the server runnig this code can access. (This is called server-side request forgery, or SSRF.) Do not allow arbitrary hosts. Instead, create an allowlist for approved hosts hardcode the correct host.

Run Locally

Run in CI

Defintion

rules:
  - id: tainted-url-host
    languages:
      - python
    message: User data flows into the host portion of this manually-constructed URL.
      This could allow an attacker to send data to their own server, potentially
      exposing sensitive data such as cookies or authorization information sent
      with this request. They could also probe internal servers or other
      resources that the server runnig this code can access. (This is called
      server-side request forgery, or SSRF.) Do not allow arbitrary hosts.
      Instead, create an allowlist for approved hosts hardcode the correct host.
    metadata:
      cwe:
        - "CWE-918: Server-Side Request Forgery (SSRF)"
      owasp:
        - A10:2021 - Server-Side Request Forgery (SSRF)
      references:
        - https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
      category: security
      technology:
        - flask
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - vuln
      impact: MEDIUM
      likelihood: MEDIUM
      confidence: MEDIUM
      vulnerability_class:
        - Server-Side Request Forgery (SSRF)
    mode: taint
    pattern-sinks:
      - patterns:
          - pattern-either:
              - patterns:
                  - pattern: '"$URLSTR" % ...'
                  - metavariable-pattern:
                      metavariable: $URLSTR
                      language: generic
                      patterns:
                        - pattern-either:
                            - pattern: $SCHEME://%s
                            - pattern: $SCHEME://%r
              - patterns:
                  - pattern: '"$URLSTR".format(...)'
                  - metavariable-pattern:
                      metavariable: $URLSTR
                      language: generic
                      pattern: $SCHEME:// { ... }
              - patterns:
                  - pattern: '"$URLSTR" + ...'
                  - metavariable-regex:
                      metavariable: $URLSTR
                      regex: .*://$
              - patterns:
                  - pattern: f"$URLSTR{...}..."
                  - metavariable-regex:
                      metavariable: $URLSTR
                      regex: .*://$
              - patterns:
                  - pattern-inside: |
                      $URL = "$URLSTR"
                      ...
                  - pattern: $URL += ...
                  - metavariable-regex:
                      metavariable: $URLSTR
                      regex: .*://$
    pattern-sources:
      - patterns:
          - pattern-either:
              - pattern: flask.request.$ANYTHING
              - patterns:
                  - pattern-inside: |
                      @$APP.route(...)
                      def $FUNC(..., $ROUTEVAR, ...):
                        ...
                  - pattern: $ROUTEVAR
    severity: WARNING

Examples

tainted-url-host.py

import os
import flask
import hashlib
import requests

app = flask.Flask(__name__)

@app.route("/route_param/<route_param>")
def route_param(route_param):
    print("blah")
    # ruleid: tainted-url-host
    url = "https://%s/path" % route_param
    requests.get(url)

    # ruleid: tainted-url-host
    url = "http://%r/path" % route_param
    requests.get(url)

    return True

@app.route("/route_param_ok/<route_param>")
def route_param_ok(route_param):
    print("blah")
    # ok: tainted-url-host
    return "<a href='https://example.com'>Click me!</a>"

@app.route("/route_param_format/<route_param>")
def route_param_format(route_param):
    print("blah")
    # ruleid: tainted-url-host
    return "<a href='https://{}/path'>Click me!</a>".format(route_param)

@app.route("/route_param_format_ok_in_path/<route_param>")
def route_param_format_ok_in_path(route_param):
    print("blah")
    # ok: tainted-url-host
    return "<a href='https://example.com/{}/path'>Click me!</a>".format(route_param)

@app.route("/route_param_percent_format/<route_param>")
def route_param_percent_format(route_param):
    print("blah")
    # ruleid: tainted-url-host
    return "<a href='https://%s/path'>Click me!</a>" % route_param

@app.route("/route_param_percent_format_ok_in_path/<route_param>")
def route_param_percent_format_ok_in_path(route_param):
    print("blah")
    # ok: tainted-url-host
    return "<a href='https://example.com/%s/path'>Click me!</a>" % route_param

@app.route("/get_param_inline", methods=["GET"])
def get_param_inline():
    # ruleid: tainted-url-host
    return "<a href='https://%s/path'>Click me!</a>" % flask.request.args.get("param")

@app.route("/get_param_inline_concat", methods=["GET"])
def get_param_inline_concat():
    # ruleid: tainted-url-host
    return "<a href='http://" + flask.request.args.get("param") + "'>Click me!</a>"

@app.route("/get_param_inline_concat_ok_in_path", methods=["GET"])
def get_param_inline_concat_ok_in_path():
    # ok: tainted-url-host
    return "<a href='http://example.com/" + flask.request.args.get("param") + "'>Click me!</a>"

@app.route("/get_param_template", methods=["GET"])
def get_param_template():
    # ruleid: tainted-url-host
    return f"<a href='https://{flask.request.args.get('param')}/path'>Click me!</a>"

@app.route("/get_param_template_ok_in_path", methods=["GET"])
def get_param_template_ok_in_path():
    # ok: tainted-url-host
    return f"<a href='https://example.com/{flask.request.args.get('param')}/path'>Click me!</a>"

@app.route("/get_param_concat", methods=["GET"])
def get_param_concat():
    param = flask.request.args.get("param")
    # ruleid: tainted-url-host
    return "<a href='https://" + param + "/path'>Click me!</a>"

@app.route("/get_param_format", methods=["GET"])
def get_param_format():
    param = flask.request.args.get("param")
    # ruleid: tainted-url-host
    return "<a href='https://{}/path'>Click me!</a>".format(param)

@app.route("/get_param_percent_format", methods=["GET"])
def get_param_percent_format():
    param = flask.request.args.get("param")
    # ruleid: tainted-url-host
    return "<a href='https://%s/path'>Click me!</a>" % (param,)

@app.route("/post_param_branch", methods=["POST"])
def post_param_branch():
    param = flask.request.form['param']
    if True:
        # ruleid: tainted-url-host
        return "<a href='https://%r/path'>Click me!</a>" % (param,)

# Real world example
@app.route('/models/<model>')
def load_model(model):
    # ruleid: tainted-url-host
    htmlpage = '''
    <body style='margin : 0px; overflow: hidden;'>
        <scene-tag embedded arjs>
            <marker-tag id="memarker" type="pattern" url="../static/patterns/pattern-kanji_qr.patt" vidhandler>
                <entity model="obj: url(https://{}/static/models.obj); mtl: url(../static/models/{}.mtl)"> </entity>
            </marker-tag>
        </scene-tag>
    </body>
    '''.format(model,model)
    return htmlpage

# Real world example
@app.route('/models/<model>')
def load_model(model):
    # ok: tainted-url-host
    htmlpage = '''
    <body style='margin : 0px; overflow: hidden;'>
        <scene-tag embedded arjs>
            <marker-tag id="memarker" type="pattern" url="../static/patterns/pattern-kanji_qr.patt" vidhandler>
                <entity model="obj: url(../static/models.obj); mtl: url(../static/models/{}.mtl)"> </entity>
            </marker-tag>
        </scene-tag>
    </body>
    '''.format(model,model)
    return htmlpage

@app.route("/const_prop")
def const_prop():
    url = "https://"
    # ruleid: tainted-url-host
    url = url + flask.request.args.get("param")

    requests.get(url)
    return True

@app.route("/add_equals")
def add_equals():
    url = "https://"
    # ruleid: tainted-url-host
    url += flask.request.args.get("param")

    requests.get(url)
    return True

@app.route("/route_param/<route_param>")
def doesnt_use_the_route_param(route_param):
    not_the_route_param = "hello.com"
    # ok: tainted-url-host
    url = "https://%s/path" % not_the_route_param
    requests.get(url)

    # ok: tainted-url-host
    url = "http://%r/path" % not_the_route_param
    requests.get(url)

    return True