-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathWebAppConstantSecretKeyFlask.qll
More file actions
139 lines (133 loc) · 5.13 KB
/
WebAppConstantSecretKeyFlask.qll
File metadata and controls
139 lines (133 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
import WebAppConstantSecretKeySource
/**
* with using flask-session package, there is no jwt exists in cookies in user side
* ```python
*import os
*from flask import Flask, session
*app = Flask(__name__)
* ```
*/
module FlaskConstantSecretKeyConfig {
/**
* `flask.Flask()`
*/
API::Node flaskInstance() {
result = API::moduleImport("flask").getMember("Flask").getASubclass*()
}
/**
* Sources are Constants that without any Tainting reach the Sinks.
* Also Sources can be the default value of getenv or similar methods
* in a case that no value is assigned to Desired SECRET_KEY environment variable
*/
predicate isSource(DataFlow::Node source) { source instanceof WebAppConstantSecretKeySource }
/**
* Sinks are one of the following kinds, some of them are directly connected to a flask Instance like
* ```python
* app.config['SECRET_KEY'] = 'CHANGEME1'
* app.secret_key = 'CHANGEME2'
* app.config.update(SECRET_KEY="CHANGEME3")
* app.config.from_mapping(SECRET_KEY="CHANGEME4")
* ```
* other Sinks are SECRET_KEY Constants Variables that are defined in separate files or a class in those files like:
* ```python
* app.config.from_pyfile("config.py")
* app.config.from_object('config.Config')
*```
* we find these files with `FromObjectFileName` DataFlow Configuration
* note that "JWT_SECRET_KEY" is same as "SECRET_KEY" but it is belong to popular flask-jwt-extended library
*/
predicate isSink(DataFlow::Node sink) {
(
exists(API::Node n | n = flaskInstance().getReturn() |
sink =
[
n.getMember("config").getSubscript(["SECRET_KEY", "JWT_SECRET_KEY"]).asSink(),
n.getMember("config")
.getMember(["update", "from_mapping"])
.getACall()
.getArgByName(["SECRET_KEY", "JWT_SECRET_KEY"])
]
)
or
exists(DataFlow::AttrWrite attr |
attr.getObject().getALocalSource() = flaskInstance().getACall() and
attr.getAttributeName() = ["secret_key", "jwt_secret_key"] and
sink = attr.getValue()
)
or
exists(SecretKeyAssignStmt e | sink.asExpr() = e.getValue())
) and
exists(sink.getScope().getLocation().getFile().getRelativePath()) and
not sink.getScope().getLocation().getFile().inStdlib()
}
/**
* An Assignments like `SECRET_KEY = ConstantValue`
* and `SECRET_KEY` file must be the Location that is specified in argument of `from_object` or `from_pyfile` methods
*/
class SecretKeyAssignStmt extends AssignStmt {
SecretKeyAssignStmt() {
exists(string configFileName, string fileNamehelper, DataFlow::Node n1, File file |
fileNamehelper = [flaskConfiFileName(n1), flaskConfiFileName2(n1)] and
// because of `from_object` we want first part of `Config.AClassName` which `Config` is a python file name
configFileName = fileNamehelper.splitAt(".") and
file = this.getLocation().getFile()
|
(
if fileNamehelper = "__name__"
then
file.getShortName() = flaskInstance().asSource().getLocation().getFile().getShortName()
else (
fileNamehelper.matches("%.py") and
file.getShortName().matches("%" + configFileName + "%") and
// after spliting, don't look at %py% pattern
configFileName != ".py"
or
// in case of referencing to a directory which then we must look for __init__.py file
not fileNamehelper.matches("%.py") and
file.getRelativePath()
.matches("%" + fileNamehelper.replaceAll(".", "/") + "/__init__.py")
)
) and
this.getTarget(0).(Name).getId() = ["SECRET_KEY", "JWT_SECRET_KEY"]
) and
exists(this.getScope().getLocation().getFile().getRelativePath()) and
not this.getScope().getLocation().getFile().inStdlib()
}
}
/**
* Holds if there is a helper predicate that specify where the Flask `SECRET_KEY` variable location is defined.
* In Flask we have config files that specify the location of `SECRET_KEY` variable initialization
* and the name of these files are determined by
* `app.config.from_pyfile("configFileName.py")`
* or
* `app.config.from_object("configFileName.ClassName")`
*/
string flaskConfiFileName(API::CallNode cn) {
cn =
flaskInstance()
.getReturn()
.getMember("config")
.getMember(["from_object", "from_pyfile"])
.getACall() and
result =
[
cn.getParameter(0).getAValueReachingSink().asExpr().(StringLiteral).getText(),
cn.getParameter(0).asSink().asExpr().(Name).getId()
]
}
string flaskConfiFileName2(API::CallNode cn) {
cn =
API::moduleImport("flask")
.getMember("Flask")
.getASubclass*()
.getASuccessor*()
.getMember("from_object")
.getACall() and
result = cn.getParameter(0).asSink().asExpr().(StringLiteral).getText()
}
}