python.flask.security.audit.xss.make-response-with-unknown-content.make-response-with-unknown-content

profile photo of semgrepsemgrep
Author
1,993
Download Count*

Be careful with flask.make_response(). If this response is rendered onto a webpage, this could create a cross-site scripting (XSS) vulnerability. flask.make_response() will not autoescape HTML. If you are rendering HTML, write your HTML in a template file and use flask.render_template() which will take care of escaping. If you are returning data from an API, consider using flask.jsonify().

Run Locally

Run in CI

Defintion

rules:
  - id: make-response-with-unknown-content
    patterns:
      - pattern: flask.make_response(...)
      - pattern-not-inside: flask.make_response()
      - pattern-not-inside: flask.make_response("...", ...)
      - pattern-not-inside: 'flask.make_response({"...": "..."}, ...)'
      - pattern-not-inside: flask.make_response(flask.redirect(...), ...)
      - pattern-not-inside: flask.make_response(flask.render_template(...), ...)
      - pattern-not-inside: flask.make_response(flask.jsonify(...), ...)
      - pattern-not-inside: flask.make_response(json.dumps(...), ...)
      - pattern-not-inside: |
          $X = flask.render_template(...)
          ...
          flask.make_response($X, ...)
      - pattern-not-inside: |
          $X = flask.jsonify(...)
          ...
          flask.make_response($X, ...)
      - pattern-not-inside: |
          $X = json.dumps(...)
          ...
          flask.make_response($X, ...)
    message: Be careful with `flask.make_response()`. If this response is rendered
      onto a webpage, this could create a cross-site scripting (XSS)
      vulnerability. `flask.make_response()` will not autoescape HTML. If you
      are rendering HTML, write your HTML in a template file and use
      `flask.render_template()` which will take care of escaping. If you are
      returning data from an API, consider using `flask.jsonify()`.
    severity: WARNING
    metadata:
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      references:
        - https://github.com/python-security/pyt//blob/093a077bcf12d1f58ddeb2d73ddc096623985fb0/examples/vulnerable_code/XSS_assign_to_other_var.py#L11
        - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
        - https://flask.palletsprojects.com/en/1.1.x/api/#response-objects
      category: security
      technology:
        - flask
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - audit
      likelihood: LOW
      impact: MEDIUM
      confidence: LOW
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Cross-Site-Scripting (XSS)
    languages:
      - python

Examples

make-response-with-unknown-content.py

# cf. https://github.com/python-security/pyt//blob/093a077bcf12d1f58ddeb2d73ddc096623985fb0/examples/vulnerable_code/

import json
import flask
from flask import Flask, request, make_response
from somewhere import fxn
app = Flask(__name__)

@app.route('/XSS_param', methods =['GET'])
def XSS1():
    param = request.args.get('param', 'not set')

    other_var = param

    html = open('templates/XSS_param.html').read()
    # ruleid: make-response-with-unknown-content
    resp = make_response(html.replace('{{ param }}', other_var))
    return resp

# cf. https://github.com/alshapton/kb-api/commit/bd649de1da9e4020f9273fff183a74edfadc0b07

def switch(target:str, config: Dict[str, str]):
    if does_base_exist(target,config):
        switch_base(target,config)
        # ruleid: make-response-with-unknown-content
        resp = (make_response(({'Switched': "The current knowledge base is now : '" + target + "'"}), 200))
    else:
        # ruleid: make-response-with-unknown-content
        resp = (make_response(({'Error': "The knowledge base '" + target + "' does not exist"}), 404))
    resp.mimetype = MIME_TYPE['json']
    return resp

# Lots of little unit tests:
# ok: make-response-with-unknown-content
make_response("hello")

# ok: make-response-with-unknown-content
make_response()

# ok: make-response-with-unknown-content
make_response({"hello": "world"}, 200)

# ok: make-response-with-unknown-content
make_response(flask.render_template("index.html"))

# ok: make-response-with-unknown-content
make_response(flask.jsonify({"hello": "world"}))

# ok: make-response-with-unknown-content
make_response(json.dumps({"hello": "world"}))

# ok: make-response-with-unknown-content
make_response(flask.redirect(unk))

t = flask.render_template("index.html")
# ok: make-response-with-unknown-content
make_response(t)

unk = fxn()

# ruleid: make-response-with-unknown-content
make_response(unk)

# ruleid: make-response-with-unknown-content
make_response("<div>" + unk + "</div>")

# ruleid: make-response-with-unknown-content
make_response({"hello": unk})

t = flask.render_template("index.html")
html = t.replace("{{ name }}", unk)
# ruleid: make-response-with-unknown-content
make_response(html)

html = """
<div>
%s
</div>
""" % unk
# ruleid: make-response-with-unknown-content
make_response(html)

if __name__ == '__main__':
    app.run(debug=True)