From a4da245809d670d58b0beb21fc433c080a017c5b Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Thu, 22 Nov 2018 15:01:02 +0100 Subject: [PATCH 1/5] Python: Implement check for flask debug mode. --- python/ql/src/Security/CWE-215/FlaskDebug.py | 9 +++++ .../ql/src/Security/CWE-215/FlaskDebug.qhelp | 39 +++++++++++++++++++ python/ql/src/Security/CWE-215/FlaskDebug.ql | 23 +++++++++++ .../Security/CWE-079/ReflectedXss.expected | 12 +++--- .../Security/CWE-215/FlaskDebug.expected | 3 ++ .../Security/CWE-215/FlaskDebug.qlref | 1 + .../test/query-tests/Security/CWE-215/options | 1 + .../test/query-tests/Security/CWE-215/test.py | 37 ++++++++++++++++++ .../Security/lib/flask/__init__.py | 3 +- 9 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 python/ql/src/Security/CWE-215/FlaskDebug.py create mode 100644 python/ql/src/Security/CWE-215/FlaskDebug.qhelp create mode 100644 python/ql/src/Security/CWE-215/FlaskDebug.ql create mode 100644 python/ql/test/query-tests/Security/CWE-215/FlaskDebug.expected create mode 100644 python/ql/test/query-tests/Security/CWE-215/FlaskDebug.qlref create mode 100644 python/ql/test/query-tests/Security/CWE-215/options create mode 100644 python/ql/test/query-tests/Security/CWE-215/test.py diff --git a/python/ql/src/Security/CWE-215/FlaskDebug.py b/python/ql/src/Security/CWE-215/FlaskDebug.py new file mode 100644 index 00000000000..683f99dcb55 --- /dev/null +++ b/python/ql/src/Security/CWE-215/FlaskDebug.py @@ -0,0 +1,9 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route('/crash') +def main(): + raise Exception() + +app.run(debug=True) diff --git a/python/ql/src/Security/CWE-215/FlaskDebug.qhelp b/python/ql/src/Security/CWE-215/FlaskDebug.qhelp new file mode 100644 index 00000000000..b7069017138 --- /dev/null +++ b/python/ql/src/Security/CWE-215/FlaskDebug.qhelp @@ -0,0 +1,39 @@ + + + +

+ Running a Flask application with debug mode enabled may allow an + attacker to gain access through the Werkzeug debugger. +

+ +
+ + +

+ Ensure that Flask applications that are run in a production + environment have debugging disabled. +

+ +
+ + +

+ Running the following code starts a Flask webserver that has + debugging enabled. By visiting /crash, it is possible + to gain access to the debugger, and run arbitrary code through the + interactive debugger. +

+ + + +
+ + +
  • Flask Quickstart Documentation: Debug Mode.
  • +
  • Werkzeug Documentation: Debugging Applications.
  • +
    + +
    + diff --git a/python/ql/src/Security/CWE-215/FlaskDebug.ql b/python/ql/src/Security/CWE-215/FlaskDebug.ql new file mode 100644 index 00000000000..1e7ce26e802 --- /dev/null +++ b/python/ql/src/Security/CWE-215/FlaskDebug.ql @@ -0,0 +1,23 @@ +/** + * @name Flask app is run in debug mode + * @description Running a Flask app in debug mode may allow an attacker to run arbitrary code through the Werkzeug debugger. + * @kind problem + * @problem.severity error + * @precision medium + * @id py/flask-debug + * @tags security + * external/cwe/cwe-215 + * external/cwe/cwe-489 + */ + +import python + +import semmle.python.web.flask.General + + +from CallNode call, Object isTrue +where + call = theFlaskClass().declaredAttribute("run").(FunctionObject).getACall() and + call.getArgByName("debug").refersTo(isTrue) and + isTrue.booleanValue() = true +select call, "A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger." diff --git a/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index b60e97ff468..f57bb944daa 100644 --- a/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -1,15 +1,15 @@ edges -| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | -| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | +| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | ../lib/flask/__init__.py:16:19:16:20 | externally controlled string | +| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | ../lib/flask/__init__.py:17:25:17:26 | externally controlled string | | reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string | | reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string | -| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | +| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | | reflected_xss.py:8:44:8:53 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | | reflected_xss.py:12:18:12:29 | dict of externally controlled string | reflected_xss.py:12:18:12:45 | externally controlled string | | reflected_xss.py:12:18:12:45 | externally controlled string | reflected_xss.py:13:51:13:60 | externally controlled string | parents -| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | -| ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | +| ../lib/flask/__init__.py:16:19:16:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | +| ../lib/flask/__init__.py:17:25:17:26 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | #select -| ../lib/flask/__init__.py:16:25:16:26 | flask.response.argument | reflected_xss.py:7:18:7:29 | dict of externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | Cross-site scripting vulnerability due to $@. | reflected_xss.py:7:18:7:29 | flask.request.args | user-provided value | +| ../lib/flask/__init__.py:17:25:17:26 | flask.response.argument | reflected_xss.py:7:18:7:29 | dict of externally controlled string | ../lib/flask/__init__.py:17:25:17:26 | externally controlled string | Cross-site scripting vulnerability due to $@. | reflected_xss.py:7:18:7:29 | flask.request.args | user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.expected b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.expected new file mode 100644 index 00000000000..3a23ea54ee2 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.expected @@ -0,0 +1,3 @@ +| test.py:10:1:10:19 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. | +| test.py:25:1:25:20 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. | +| test.py:29:1:29:20 | ControlFlowNode for Attribute() | A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger. | diff --git a/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.qlref b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.qlref new file mode 100644 index 00000000000..0e21a3ac14f --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/FlaskDebug.qlref @@ -0,0 +1 @@ +Security/CWE-215/FlaskDebug.ql diff --git a/python/ql/test/query-tests/Security/CWE-215/options b/python/ql/test/query-tests/Security/CWE-215/options new file mode 100644 index 00000000000..84717fe64cf --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=2 -p ../lib diff --git a/python/ql/test/query-tests/Security/CWE-215/test.py b/python/ql/test/query-tests/Security/CWE-215/test.py new file mode 100644 index 00000000000..6054caf1d33 --- /dev/null +++ b/python/ql/test/query-tests/Security/CWE-215/test.py @@ -0,0 +1,37 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route('/crash') +def main(): + raise Exception() + +# bad +app.run(debug=True) + +# okay +app.run() +app.run(debug=False) + +# also okay +run(debug=True) + +app.notrun(debug=True) + +# a slightly more involved example using flow and truthy values + +DEBUG = True + +app.run(debug=DEBUG) + +DEBUG = 1 + +app.run(debug=DEBUG) + +if False: + app.run(debug=True) + +# false negative + +runapp = app.run +runapp(debug=True) diff --git a/python/ql/test/query-tests/Security/lib/flask/__init__.py b/python/ql/test/query-tests/Security/lib/flask/__init__.py index 033c49b93cd..0e2510ff695 100644 --- a/python/ql/test/query-tests/Security/lib/flask/__init__.py +++ b/python/ql/test/query-tests/Security/lib/flask/__init__.py @@ -1,7 +1,8 @@ class Flask(object): - pass + def run(self, *args, **kwargs): + pass from .globals import request From b393d9ad045d183884c7c1cf2d214089410c39a8 Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Tue, 27 Nov 2018 15:21:02 +0100 Subject: [PATCH 2/5] Add change note. --- change-notes/1.19/analysis-python.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.19/analysis-python.md b/change-notes/1.19/analysis-python.md index d4e7212593f..10b7d36f4d4 100644 --- a/change-notes/1.19/analysis-python.md +++ b/change-notes/1.19/analysis-python.md @@ -56,6 +56,7 @@ A new predicate `Stmt.getAnEntryNode()` has been added to make it easier to writ | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| +| Flask app is run in debug mode (`py/flask-debug`) | security, external/cwe/cwe-215, external/cwe/cwe-489 | Finds instances where a Flask application is run in debug mode. Enabled on LGTM by default. | | Information exposure through an exception (`py/stack-trace-exposure`) | security, external/cwe/cwe-209, external/cwe/cwe-497 | Finds instances where information about an exception may be leaked to an external user. Enabled on LGTM by default. | ## Changes to existing queries From 8d341ab4674c2f1fedea29033efb2a8578e92a4f Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Tue, 27 Nov 2018 16:56:09 +0100 Subject: [PATCH 3/5] Fix stub file. --- python/ql/test/query-tests/Security/lib/flask/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/ql/test/query-tests/Security/lib/flask/__init__.py b/python/ql/test/query-tests/Security/lib/flask/__init__.py index 0e2510ff695..9277dc33563 100644 --- a/python/ql/test/query-tests/Security/lib/flask/__init__.py +++ b/python/ql/test/query-tests/Security/lib/flask/__init__.py @@ -1,8 +1,7 @@ class Flask(object): - def run(self, *args, **kwargs): - pass + def run(self, *args, **kwargs): pass from .globals import request From 6ebf504d979976b6205cf1ba0625cf18fcf0328a Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Tue, 27 Nov 2018 16:59:19 +0100 Subject: [PATCH 4/5] Update test results after stub change. --- .../Security/CWE-079/ReflectedXss.expected | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index f57bb944daa..b60e97ff468 100644 --- a/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/python/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -1,15 +1,15 @@ edges -| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | ../lib/flask/__init__.py:16:19:16:20 | externally controlled string | -| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | ../lib/flask/__init__.py:17:25:17:26 | externally controlled string | +| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | +| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | | reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string | | reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string | -| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | +| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | | reflected_xss.py:8:44:8:53 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | | reflected_xss.py:12:18:12:29 | dict of externally controlled string | reflected_xss.py:12:18:12:45 | externally controlled string | | reflected_xss.py:12:18:12:45 | externally controlled string | reflected_xss.py:13:51:13:60 | externally controlled string | parents +| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | -| ../lib/flask/__init__.py:16:19:16:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | -| ../lib/flask/__init__.py:17:25:17:26 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | +| ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | #select -| ../lib/flask/__init__.py:17:25:17:26 | flask.response.argument | reflected_xss.py:7:18:7:29 | dict of externally controlled string | ../lib/flask/__init__.py:17:25:17:26 | externally controlled string | Cross-site scripting vulnerability due to $@. | reflected_xss.py:7:18:7:29 | flask.request.args | user-provided value | +| ../lib/flask/__init__.py:16:25:16:26 | flask.response.argument | reflected_xss.py:7:18:7:29 | dict of externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | Cross-site scripting vulnerability due to $@. | reflected_xss.py:7:18:7:29 | flask.request.args | user-provided value | From 7f94c257a7c08a4b86ca11740364cd0200eaaf99 Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Tue, 27 Nov 2018 19:02:44 +0100 Subject: [PATCH 5/5] Change precision to `high`. --- python/ql/src/Security/CWE-215/FlaskDebug.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Security/CWE-215/FlaskDebug.ql b/python/ql/src/Security/CWE-215/FlaskDebug.ql index 1e7ce26e802..6886e419213 100644 --- a/python/ql/src/Security/CWE-215/FlaskDebug.ql +++ b/python/ql/src/Security/CWE-215/FlaskDebug.ql @@ -3,7 +3,7 @@ * @description Running a Flask app in debug mode may allow an attacker to run arbitrary code through the Werkzeug debugger. * @kind problem * @problem.severity error - * @precision medium + * @precision high * @id py/flask-debug * @tags security * external/cwe/cwe-215