Skip to content

Commit 7a17b99

Browse files
committed
V2
1 parent e3e0307 commit 7a17b99

File tree

15 files changed

+484
-251
lines changed

15 files changed

+484
-251
lines changed

python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/ConstantSecretKey.qhelp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
</p>
1616
</recommendation>
1717
<example>
18-
19-
<sample src="example_bad.py" />
20-
21-
<sample src="example_good.py" />
22-
18+
<sample src="examples/example_Django_safe.py" />
19+
<sample src="examples/example_Django_snsafe.py" />
20+
<sample src="examples/example_Flask_safe.py" />
21+
<sample src="examples/example_Flask_unsafe.py" />
22+
<sample src="examples/example_Flask_unsafe2.py" />
2323
</example>
2424
<references>
2525
<li>

python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/ConstantSecretKey.ql

Lines changed: 0 additions & 219 deletions
This file was deleted.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @name Initializing SECRET_KEY of Flask application with Constant value
3+
* @description Initializing SECRET_KEY of Flask application with Constant value
4+
* files can lead to Authentication bypass
5+
* @kind path-problem
6+
* @id py/flask-constant-secret-key
7+
* @problem.severity error
8+
* @security-severity 8.5
9+
* @precision high
10+
* @tags security
11+
* experimental
12+
* external/cwe/cwe-287
13+
*/
14+
15+
import python
16+
import experimental.semmle.python.Concepts
17+
import semmle.python.dataflow.new.DataFlow
18+
import semmle.python.ApiGraphs
19+
import semmle.python.dataflow.new.TaintTracking
20+
import WebAppConstantSecretKeyDjango
21+
import WebAppConstantSecretKeyFlask
22+
23+
newtype TFrameWork =
24+
Flask() or
25+
Django()
26+
27+
module WebAppConstantSecretKeyConfig implements DataFlow::StateConfigSig {
28+
class FlowState = TFrameWork;
29+
30+
predicate isSource(DataFlow::Node source, FlowState state) {
31+
state = Flask() and FlaskConstantSecretKeyConfig::isSource(source)
32+
or
33+
state = Django() and DjangoConstantSecretKeyConfig::isSource(source)
34+
}
35+
36+
predicate isSink(DataFlow::Node sink, FlowState state) {
37+
state = Flask() and FlaskConstantSecretKeyConfig::isSink(sink)
38+
or
39+
state = Django() and DjangoConstantSecretKeyConfig::isSink(sink)
40+
}
41+
42+
predicate isBarrier(DataFlow::Node node, FlowState state) { none() }
43+
44+
predicate isAdditionalFlowStep(
45+
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
46+
) {
47+
none()
48+
}
49+
}
50+
51+
module WebAppConstantSecretKey = TaintTracking::GlobalWithState<WebAppConstantSecretKeyConfig>;
52+
53+
import WebAppConstantSecretKey::PathGraph
54+
55+
from WebAppConstantSecretKey::PathNode source, WebAppConstantSecretKey::PathNode sink
56+
where WebAppConstantSecretKey::flowPath(source, sink)
57+
select sink, source, sink, "The SECRET_KEY config variable is assigned by $@.", source,
58+
" this constant String"
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import python
2+
import experimental.semmle.python.Concepts
3+
import semmle.python.dataflow.new.DataFlow
4+
import semmle.python.ApiGraphs
5+
import semmle.python.dataflow.new.TaintTracking
6+
7+
module DjangoConstantSecretKeyConfig {
8+
/**
9+
* Sources are Constants that without any Tainting reach the Sinks.
10+
* Also Sources can be the default value of getenv or similar methods
11+
* in a case that no value is assigned to Desired SECRET_KEY environment variable
12+
*/
13+
predicate isSource(DataFlow::Node source) {
14+
(
15+
source.asExpr().isConstant()
16+
or
17+
exists(API::Node cn |
18+
cn =
19+
[
20+
API::moduleImport("configparser")
21+
.getMember(["ConfigParser", "RawConfigParser"])
22+
.getReturn(),
23+
// legacy API https://docs.python.org/3/library/configparser.html#legacy-api-examples
24+
API::moduleImport("configparser")
25+
.getMember(["ConfigParser", "RawConfigParser"])
26+
.getReturn()
27+
.getMember("get")
28+
.getReturn()
29+
] and
30+
source = cn.asSource()
31+
)
32+
or
33+
exists(API::CallNode cn |
34+
cn =
35+
[
36+
API::moduleImport("os").getMember("getenv").getACall(),
37+
API::moduleImport("os").getMember("environ").getMember("get").getACall()
38+
] and
39+
(
40+
// this can be ideal if we assume that best security practice is that
41+
// we don't get SECRET_KEY from env and we always assign a secure generated random string to it
42+
cn.getNumArgument() = 1
43+
or
44+
cn.getNumArgument() = 2 and
45+
DataFlow::localFlow(any(DataFlow::Node n | n.asExpr().isConstant()), cn.getArg(1))
46+
) and
47+
source.asExpr() = cn.asExpr()
48+
)
49+
or
50+
source.asExpr() =
51+
API::moduleImport("os").getMember("environ").getASubscript().asSource().asExpr()
52+
) and
53+
// followings will sanitize the get_random_secret_key of django.core.management.utils and similar random generators which we have their source code and some of them can be tracking by taint tracking because they are initilized by a constant!
54+
exists(source.getScope().getLocation().getFile().getRelativePath()) and
55+
// special case for get_random_secret_key
56+
not source.getScope().getLocation().getFile().getBaseName() = "crypto.py"
57+
}
58+
59+
/**
60+
* A sink like following SECRET_KEY Assignments
61+
* ```python
62+
*from django.conf import settings
63+
*settings.configure(
64+
* SECRET_KEY="constant",
65+
*)
66+
* # or
67+
*settings.SECRET_KEY = "constant"
68+
* ```
69+
*/
70+
predicate isSink(DataFlow::Node sink) {
71+
exists(API::moduleImport("django")) and
72+
(
73+
exists(AssignStmt e | e.getTarget(0).(Name).getId() = ["SECRET_KEY", "SECRET_KEY_FALLBACKS"] |
74+
sink.asExpr() = e.getValue()
75+
)
76+
or
77+
exists(API::Node settings |
78+
settings =
79+
API::moduleImport("django").getMember("conf").getMember(["global_settings", "settings"]) and
80+
sink =
81+
settings
82+
.getMember("configure")
83+
.getKeywordParameter(["SECRET_KEY_FALLBACKS", "SECRET_KEY"])
84+
.asSink()
85+
)
86+
or
87+
exists(API::Node n, DataFlow::AttrWrite attr |
88+
attr.getObject().getALocalSource() = n.asSource() and
89+
attr.getAttributeName() = ["SECRET_KEY_FALLBACKS", "SECRET_KEY"] and
90+
sink = attr.getValue()
91+
)
92+
) and
93+
exists(sink.getScope().getLocation().getFile().getRelativePath())
94+
}
95+
}

0 commit comments

Comments
 (0)