Skip to content

Commit c704158

Browse files
committed
remove sources which are contained from environment variables, fix some bugs thanks to @yoff
1 parent 13fa08a commit c704158

File tree

9 files changed

+39
-54
lines changed

9 files changed

+39
-54
lines changed

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ import semmle.python.dataflow.new.TaintTracking
2020
import WebAppConstantSecretKeyDjango
2121
import WebAppConstantSecretKeyFlask
2222

23+
private predicate stringConstCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
24+
exists(CompareNode cn | cn = g |
25+
exists(StrConst str_const, Cmpop op |
26+
op = any(Eq eq) and branch = false
27+
or
28+
op = any(NotEq ne) and branch = true
29+
|
30+
cn.operands(str_const.getAFlowNode(), op, node)
31+
or
32+
cn.operands(node, op, str_const.getAFlowNode())
33+
)
34+
)
35+
}
36+
37+
class StringConstCompareBarrier extends DataFlow::Node {
38+
StringConstCompareBarrier() {
39+
this = DataFlow::BarrierGuard<stringConstCompare/3>::getABarrierNode()
40+
}
41+
}
42+
2343
newtype TFrameWork =
2444
Flask() or
2545
Django()
@@ -39,7 +59,10 @@ module WebAppConstantSecretKeyConfig implements DataFlow::StateConfigSig {
3959
state = Django() and DjangoConstantSecretKeyConfig::isSink(sink)
4060
}
4161

42-
predicate isBarrier(DataFlow::Node node, FlowState state) { none() }
62+
predicate isBarrier(DataFlow::Node sanitizer, FlowState state) {
63+
(state = Flask() or state = Django()) and
64+
sanitizer instanceof StringConstCompareBarrier
65+
}
4366

4467
predicate isAdditionalFlowStep(
4568
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ module DjangoConstantSecretKeyConfig {
4141
.asSink()
4242
)
4343
or
44-
exists(API::Node n, DataFlow::AttrWrite attr |
45-
attr.getObject().getALocalSource() = n.asSource() and
44+
exists(DataFlow::AttrWrite attr |
4645
attr.getAttributeName() = ["SECRET_KEY_FALLBACKS", "SECRET_KEY"] and
4746
sink = attr.getValue()
4847
)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ module FlaskConstantSecretKeyConfig {
5757
]
5858
)
5959
or
60-
exists(API::Node n, DataFlow::AttrWrite attr | n = flaskInstance() |
61-
attr.getObject().getALocalSource() = n.getACall() and
60+
exists(DataFlow::AttrWrite attr |
61+
attr.getObject().getALocalSource() = flaskInstance().getACall() and
6262
attr.getAttributeName() = ["secret_key", "jwt_secret_key"] and
6363
sink = attr.getValue()
6464
)

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

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,29 @@ import semmle.python.ApiGraphs
66
class WebAppConstantSecretKeySource extends DataFlow::Node {
77
WebAppConstantSecretKeySource() {
88
(
9-
// because Env return an Exeption if there isan't any value (instead of none) we should check whether
10-
// there is a default value of there is a config file which mostly these config files have a default value
11-
exists(API::Node env | env = API::moduleImport("environ").getMember("Env") |
12-
(
13-
// has default value
14-
exists(env.getKeywordParameter("SECRET_KEY").asSource())
15-
or
16-
// get value from a config file which is not best security practice
17-
exists(env.getReturn().getMember("read_env"))
9+
// because Env return an Exeption if there isan't any value
10+
// we should check whether there is a default value or not
11+
exists(API::Node env |
12+
env = API::moduleImport("environ").getMember("Env") and
13+
// has default value
14+
exists(API::Node param | param = env.getKeywordParameter("SECRET_KEY") |
15+
param.asSink().asExpr().getASubExpression*().isConstant()
1816
) and
1917
this = env.getReturn().getReturn().asSource()
2018
)
2119
or
2220
this.asExpr().isConstant()
2321
or
24-
exists(API::Node cn |
25-
cn =
26-
[
27-
API::moduleImport("configparser")
28-
.getMember(["ConfigParser", "RawConfigParser"])
29-
.getReturn(),
30-
// legacy API https://docs.python.org/3/library/configparser.html#legacy-api-examples
31-
API::moduleImport("configparser")
32-
.getMember(["ConfigParser", "RawConfigParser"])
33-
.getReturn()
34-
.getMember("get")
35-
.getReturn()
36-
] and
37-
this = cn.asSource()
38-
)
39-
or
4022
exists(API::CallNode cn |
4123
cn =
4224
[
4325
API::moduleImport("os").getMember("getenv").getACall(),
4426
API::moduleImport("os").getMember("environ").getMember("get").getACall()
4527
] and
46-
(
47-
// this can be ideal if we assume that best security practice is that
48-
// we don't get SECRET_KEY from env and we always assign a secure generated random string to it
49-
cn.getNumArgument() = 1
50-
or
51-
cn.getNumArgument() = 2 and
52-
DataFlow::localFlow(any(DataFlow::Node n | n.asExpr().isConstant()), cn.getArg(1))
53-
) and
28+
cn.getNumArgument() = 2 and
29+
DataFlow::localFlow(any(DataFlow::Node n | n.asExpr().isConstant()), cn.getArg(1)) and
5430
this.asExpr() = cn.asExpr()
5531
)
56-
or
57-
this = API::moduleImport("os").getMember("environ").getASubscript().asSource()
5832
) and
5933
// 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!
6034
exists(this.getScope().getLocation().getFile().getRelativePath()) and
Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
"""Flask App configuration."""
2-
from os import environ
32
import os
43
import random
5-
import configparser
64

75
FLASK_DEBUG = True
86
aConstant = 'CHANGEME2'
9-
config = configparser.ConfigParser()
107

118

129
class Config:
13-
SECRET_KEY = config["a"]["Secret"]
14-
SECRET_KEY = config.get("key", "value")
15-
SECRET_KEY = environ.get("envKey")
1610
SECRET_KEY = aConstant
17-
SECRET_KEY = os.getenv('envKey')
18-
SECRET_KEY = os.environ.get('envKey')
1911
SECRET_KEY = os.environ.get('envKey', random.randint)
2012
SECRET_KEY = os.getenv('envKey', random.randint)
2113
SECRET_KEY = os.getenv('envKey', aConstant)
2214
SECRET_KEY = os.environ.get('envKey', aConstant)
23-
SECRET_KEY = os.environ['envKey']

python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/examples/config2.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"""Flask App configuration."""
2-
import environ
32

43
aConstant = 'CHANGEME2'
54

python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/examples/config3.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
# General Config
44
FLASK_DEBUG = True
55
SECRET_KEY = "CHANGEME5"
6-
if SECRET_KEY == "CHANGEaME5":
6+
if SECRET_KEY == "CHANGEME5":
77
raise "not possible"

python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/examples/example_Django_unsafe.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
from django.urls import path
66
from django.http import HttpResponse
77

8-
98
env = environ.Env(
109
SECRET_KEY=(str, "AConstantKey")
1110
)
1211
env.read_env(env_file='.env')
13-
# following is not safe if there is a call to read_env or if there is default value in Env(..)
12+
# following is not safe if there is default value in Env(..)
1413
settings.SECRET_KEY = env('SECRET_KEY')
1514

1615
settings.configure(

python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/examples/example_Flask_safe.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
def DEB_EX():
1111
if 'logged_in' not in session:
1212
session['logged_in'] = 'value'
13-
# debugging whether secret_key is secure or not
13+
# debuggin whether secret_key is secure or not
1414
return app.secret_key
1515

1616

0 commit comments

Comments
 (0)