diff --git a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll index 0ef2234a5772..22cac847e694 100644 --- a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll @@ -10,6 +10,7 @@ private import semmle.python.Concepts private import semmle.python.frameworks.data.ModelsAsData private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards +private import semmle.python.ApiGraphs /** * Provides default sources, sinks and sanitizers for detecting @@ -91,4 +92,15 @@ module ReflectedXss { class SanitizerFromModel extends Sanitizer { SanitizerFromModel() { ModelOutput::barrierNode(this, ["html-injection", "js-injection"]) } } + + /** + * A call to `json.dumps()`, considered as a sanitizer. + * The output of json.dumps is JSON-formatted data typically returned + * with application/json Content-Type, which browsers do not render as HTML. + */ + class JsonDumpsSanitizer extends Sanitizer { + JsonDumpsSanitizer() { + this = API::moduleImport("json").getMember("dumps").getACall() + } + } } diff --git a/python/ql/test/query-tests/Security/CWE-079-ReflectedXss/json_response.py b/python/ql/test/query-tests/Security/CWE-079-ReflectedXss/json_response.py new file mode 100644 index 000000000000..2d269c8f8bf3 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-079-ReflectedXss/json_response.py @@ -0,0 +1,19 @@ +import json +from flask import Flask, request, make_response + +app = Flask(__name__) + + +@app.route("/api/data") +def json_api_response(): + """json.dumps output is safe - JSON-encoded data is not rendered as HTML.""" + user_input = request.args.get("data", "") + result = json.dumps({"input": user_input}) + return make_response(result, 200, {"Content-Type": "application/json"}) # Safe + + +@app.route("/unsafe") +def unsafe_html_response(): + """Without json.dumps, user input in HTML response is unsafe.""" + user_input = request.args.get("data", "") + return make_response("" + user_input + "") # $ Alert