diff --git a/change-notes/1.19/analysis-python.md b/change-notes/1.19/analysis-python.md index 3d49ad43c89..177a10a0ce2 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. | | Request without certificate validation (`py/request-without-cert-validation`) | security, external/cwe/cwe-295 | Finds requests where certificate verification has been explicitly turned off, possibly allowing man-in-the-middle attacks. Not enabled on LGTM by default. | 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..6886e419213 --- /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 high + * @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-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..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,7 +1,7 @@ class Flask(object): - pass + def run(self, *args, **kwargs): pass from .globals import request