diff --git a/python/ql/src/experimental/Security/CWE-079/ReflectedXSS.ql b/python/ql/src/experimental/Security/CWE-079/ReflectedXSS.ql new file mode 100644 index 00000000000..008650c86e7 --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-079/ReflectedXSS.ql @@ -0,0 +1,23 @@ +/** + * @name Reflected server-side cross-site scripting + * @description Writing user input directly to a web page + * allows for a cross-site scripting vulnerability. + * @kind path-problem + * @problem.severity error + * @security-severity 2.9 + * @sub-severity high + * @id py/reflective-xss + * @tags security + * external/cwe/cwe-079 + * external/cwe/cwe-116 + */ + +// determine precision above +import python +import experimental.semmle.python.security.dataflow.ReflectedXSS +import DataFlow::PathGraph + +from ReflectedXssConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.", + source.getNode(), "a user-provided value" diff --git a/python/ql/src/experimental/Security/CWE-079/test.ql b/python/ql/src/experimental/Security/CWE-079/test.ql deleted file mode 100644 index e30d45c0c3f..00000000000 --- a/python/ql/src/experimental/Security/CWE-079/test.ql +++ /dev/null @@ -1,3 +0,0 @@ -select "1" -// void query to run and generate unit_tests.testproj database to test -// until we decide the objective of the query diff --git a/python/ql/src/experimental/Security/CWE-079/unit_tests/ReflectedXSS.qlref b/python/ql/src/experimental/Security/CWE-079/unit_tests/ReflectedXSS.qlref new file mode 100644 index 00000000000..dec87309b29 --- /dev/null +++ b/python/ql/src/experimental/Security/CWE-079/unit_tests/ReflectedXSS.qlref @@ -0,0 +1 @@ +experimental/Security/CWE-079/ReflectedXSS.ql diff --git a/python/ql/src/experimental/Security/CWE-079/unit_tests/flask_mail.py b/python/ql/src/experimental/Security/CWE-079/unit_tests/flask_mail.py index 7f02b6db10f..8b1494b539f 100644 --- a/python/ql/src/experimental/Security/CWE-079/unit_tests/flask_mail.py +++ b/python/ql/src/experimental/Security/CWE-079/unit_tests/flask_mail.py @@ -1,7 +1,6 @@ # https://pythonhosted.org/Flask-Mail/ # https://github.com/mattupstate/flask-mail/blob/1709c70d839a7cc7b1f7eeb97333b71cd420fe32/flask_mail.py#L239 -# tmp: this test cover RFS to any part of the message, but can be shortened to a specific part (body&html) once we decide the objective of the query. from flask_mail import Mail, Message app = Flask(__name__) @@ -9,15 +8,15 @@ mail = Mail(app) @app.route("/send") def send(): - msg = Message(subject=request.args["subject"], - sender=request.args["sender"], - recipients=list(request.args["recipient"]), - body=request.args["body"], + msg = Message(subject="Subject", + sender="from@example.com", + recipients=["to@example.com"], + body="body", html=request.args["html"]) # The message can contain a body and/or HTML: - msg.body = "test" - msg.html = "test" + msg.body = "body" + msg.html = request.args["html"] mail.send(msg) @@ -27,8 +26,8 @@ def connect(): Minimal example to test mail.connect() usage """ with mail.connect() as conn: - msg = Message(subject=request.args["subject"], - sender=request.args["sender"], - recipients=list(request.args["recipient"]), - body=request.args["html"]) + msg = Message(subject="Subject", + sender="from@example.com", + recipients=["to@example.com"], + html=request.args["html"]) conn.send(msg) diff --git a/python/ql/src/experimental/Security/CWE-079/unit_tests/test.actual b/python/ql/src/experimental/Security/CWE-079/unit_tests/test.actual deleted file mode 100644 index 2a4f078a25f..00000000000 --- a/python/ql/src/experimental/Security/CWE-079/unit_tests/test.actual +++ /dev/null @@ -1 +0,0 @@ -| 1 | diff --git a/python/ql/src/experimental/Security/CWE-079/unit_tests/test.qlref b/python/ql/src/experimental/Security/CWE-079/unit_tests/test.qlref deleted file mode 100644 index 01c9dd06163..00000000000 --- a/python/ql/src/experimental/Security/CWE-079/unit_tests/test.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE-079/test.ql diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/ReflectedXSS.qll b/python/ql/src/experimental/semmle/python/security/dataflow/ReflectedXSS.qll new file mode 100644 index 00000000000..eb8a3144ecf --- /dev/null +++ b/python/ql/src/experimental/semmle/python/security/dataflow/ReflectedXSS.qll @@ -0,0 +1,29 @@ +/** + * Provides a taint-tracking configuration for detecting reflected server-side + * cross-site scripting vulnerabilities. + */ + +import python +import semmle.python.dataflow.new.DataFlow +import semmle.python.dataflow.new.TaintTracking +import semmle.python.dataflow.new.RemoteFlowSources +import semmle.python.dataflow.new.BarrierGuards +import experimental.semmle.python.Concepts + +/** + * A taint-tracking configuration for detecting reflected server-side cross-site + * scripting vulnerabilities. + */ +class ReflectedXssConfiguration extends TaintTracking::Configuration { + ReflectedXssConfiguration() { this = "ReflectedXssConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + sink = any(EmailSender email).getHtmlBody() + } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof StringConstCompare + } +}