Skip to content

Commit a4da245

Browse files
committed
Python: Implement check for flask debug mode.
1 parent a85dfb1 commit a4da245

File tree

9 files changed

+121
-7
lines changed

9 files changed

+121
-7
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from flask import Flask
2+
3+
app = Flask(__name__)
4+
5+
@app.route('/crash')
6+
def main():
7+
raise Exception()
8+
9+
app.run(debug=True)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Running a Flask application with debug mode enabled may allow an
8+
attacker to gain access through the Werkzeug debugger.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
Ensure that Flask applications that are run in a production
16+
environment have debugging disabled.
17+
</p>
18+
19+
</recommendation>
20+
<example>
21+
22+
<p>
23+
Running the following code starts a Flask webserver that has
24+
debugging enabled. By visiting <code>/crash</code>, it is possible
25+
to gain access to the debugger, and run arbitrary code through the
26+
interactive debugger.
27+
</p>
28+
29+
<sample src="FlaskDebug.py" />
30+
31+
</example>
32+
33+
<references>
34+
<li>Flask Quickstart Documentation: <a href="http://flask.pocoo.org/docs/1.0/quickstart/#debug-mode">Debug Mode</a>.</li>
35+
<li>Werkzeug Documentation: <a href="http://werkzeug.pocoo.org/docs/0.14/debug/">Debugging Applications</a>.</li>
36+
</references>
37+
38+
</qhelp>
39+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @name Flask app is run in debug mode
3+
* @description Running a Flask app in debug mode may allow an attacker to run arbitrary code through the Werkzeug debugger.
4+
* @kind problem
5+
* @problem.severity error
6+
* @precision medium
7+
* @id py/flask-debug
8+
* @tags security
9+
* external/cwe/cwe-215
10+
* external/cwe/cwe-489
11+
*/
12+
13+
import python
14+
15+
import semmle.python.web.flask.General
16+
17+
18+
from CallNode call, Object isTrue
19+
where
20+
call = theFlaskClass().declaredAttribute("run").(FunctionObject).getACall() and
21+
call.getArgByName("debug").refersTo(isTrue) and
22+
isTrue.booleanValue() = true
23+
select call, "A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger."
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
edges
2-
| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string |
3-
| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string |
2+
| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | ../lib/flask/__init__.py:16:19:16:20 | externally controlled string |
3+
| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | ../lib/flask/__init__.py:17:25:17:26 | externally controlled string |
44
| reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string |
55
| reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string |
6-
| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string |
6+
| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:15:19:15:20 | externally controlled string |
77
| reflected_xss.py:8:44:8:53 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
88
| reflected_xss.py:12:18:12:29 | dict of externally controlled string | reflected_xss.py:12:18:12:45 | externally controlled string |
99
| reflected_xss.py:12:18:12:45 | externally controlled string | reflected_xss.py:13:51:13:60 | externally controlled string |
1010
parents
11-
| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
1211
| ../lib/flask/__init__.py:15:19:15:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
13-
| ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
12+
| ../lib/flask/__init__.py:16:19:16:20 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
13+
| ../lib/flask/__init__.py:17:25:17:26 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
1414
#select
15-
| ../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 |
15+
| ../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 |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| 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. |
2+
| 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. |
3+
| 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. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-215/FlaskDebug.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
semmle-extractor-options: --max-import-depth=2 -p ../lib
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from flask import Flask
2+
3+
app = Flask(__name__)
4+
5+
@app.route('/crash')
6+
def main():
7+
raise Exception()
8+
9+
# bad
10+
app.run(debug=True)
11+
12+
# okay
13+
app.run()
14+
app.run(debug=False)
15+
16+
# also okay
17+
run(debug=True)
18+
19+
app.notrun(debug=True)
20+
21+
# a slightly more involved example using flow and truthy values
22+
23+
DEBUG = True
24+
25+
app.run(debug=DEBUG)
26+
27+
DEBUG = 1
28+
29+
app.run(debug=DEBUG)
30+
31+
if False:
32+
app.run(debug=True)
33+
34+
# false negative
35+
36+
runapp = app.run
37+
runapp(debug=True)

python/ql/test/query-tests/Security/lib/flask/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11

22

33
class Flask(object):
4-
pass
4+
def run(self, *args, **kwargs):
5+
pass
56

67
from .globals import request
78

0 commit comments

Comments
 (0)