-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathHardcodedCredentials.ql
More file actions
143 lines (121 loc) · 3.74 KB
/
HardcodedCredentials.ql
File metadata and controls
143 lines (121 loc) · 3.74 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
140
141
142
143
/**
* @name Hard-coded credentials
* @description Credentials are hard coded in the source code of the application.
* @kind problem
* @problem.severity error
* @precision medium
* @id py/hardcoded-credentials
* @tags security
* external/cwe/cwe-259
* external/cwe/cwe-321
* external/cwe/cwe-798
*/
import semmle.python.security.TaintTracking
import semmle.python.filters.Tests
class HardcodedValue extends TaintKind {
HardcodedValue() {
this = "hard coded value"
}
}
bindingset[char, fraction]
predicate fewer_characters_than(StrConst str, string char, float fraction) {
exists(string text, int chars |
text = str.getText() and
chars = count(int i | text.charAt(i) = char) |
/* Allow one character */
chars = 1 or
chars < text.length() * fraction
)
}
predicate possible_reflective_name(string name) {
exists(any(ModuleObject m).getAttribute(name))
or
exists(any(ClassObject c).lookupAttribute(name))
or
any(ClassObject c).getName() = name
or
any(ModuleObject m).getName() = name
or
exists(builtin_object(name))
}
int char_count(StrConst str) {
result = count(string c | c = str.getText().charAt(_))
}
predicate capitalized_word(StrConst str) {
str.getText().regexpMatch("[A-Z][a-z]+")
}
predicate maybeCredential(ControlFlowNode f) {
/* A string that is not too short and unlikely to be text or an identifier. */
exists(StrConst str |
str = f.getNode() |
/* At least 10 characters */
str.getText().length() > 9 and
/* Not too much whitespace */
fewer_characters_than(str, " ", 0.05) and
/* or underscores */
fewer_characters_than(str, "_", 0.2) and
/* Not too repetitive */
exists(int chars |
chars = char_count(str) |
chars > 20 or
chars > str.getText().length()/2
) and
not possible_reflective_name(str.getText()) and
not capitalized_word(str)
)
or
/* Or, an integer with at least 8 digits */
exists(IntegerLiteral lit |
f.getNode() = lit
|
not exists(lit.getValue())
or
lit.getValue() > 10000000
)
}
class HardcodedValueSource extends TaintSource {
HardcodedValueSource() {
maybeCredential(this)
}
override predicate isSourceOf(TaintKind kind) {
kind instanceof HardcodedValue
}
}
class CredentialSink extends TaintSink {
CredentialSink() {
exists(string name |
name.regexpMatch(getACredentialRegex()) and
not name.suffix(name.length()-4) = "file"
|
any(FunctionObject func).getNamedArgumentForCall(_, name) = this
or
exists(Keyword k |
k.getArg() = name and k.getValue().getAFlowNode() = this
)
or
exists(CompareNode cmp, NameNode n |
n.getId() = name
|
cmp.operands(this, any(Eq eq), n)
or
cmp.operands(n, any(Eq eq), this)
)
)
}
override predicate sinks(TaintKind kind) {
kind instanceof HardcodedValue
}
}
/**
* Gets a regular expression for matching names of locations (variables, parameters, keys) that
* indicate the value being held is a credential.
*/
private string getACredentialRegex() {
result = "(?i).*pass(wd|word|code|phrase)(?!.*question).*" or
result = "(?i).*(puid|username|userid).*" or
result = "(?i).*(cert)(?!.*(format|name)).*"
}
from TaintSource src, TaintSink sink
where src.flowsToSink(sink) and
not any(TestScope test).contains(src.(ControlFlowNode).getNode())
select sink, "Use of hardcoded credentials from $@.", src, src.toString()