-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathHardcodedCredentials.ql
161 lines (138 loc) · 4.77 KB
/
HardcodedCredentials.ql
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* @name Hard-coded credentials
* @description Credentials are hard coded in the source code of the application.
* @kind path-problem
* @problem.severity error
* @security-severity 9.8
* @precision medium
* @id rb/hardcoded-credentials
* @tags security
* external/cwe/cwe-259
* external/cwe/cwe-321
* external/cwe/cwe-798
*/
import codeql.ruby.AST
import codeql.ruby.DataFlow
import codeql.ruby.TaintTracking
import codeql.ruby.controlflow.CfgNodes
private string getValueText(StringLiteral sl) { result = sl.getConstantValue().getString() }
bindingset[char, fraction]
predicate fewer_characters_than(StringLiteral str, string char, float fraction) {
exists(string text, int chars |
text = getValueText(str) 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) {
// TODO: implement this?
none()
}
int char_count(StringLiteral str) { result = count(string c | c = getValueText(str).charAt(_)) }
predicate capitalized_word(StringLiteral str) { getValueText(str).regexpMatch("[A-Z][a-z]+") }
predicate format_string(StringLiteral str) { getValueText(str).matches("%{%}%") }
predicate maybeCredential(Expr e) {
/* A string that is not too short and unlikely to be text or an identifier. */
exists(StringLiteral str | str = e |
/* At least 10 characters */
getValueText(str).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 > 15 or
chars * 3 > getValueText(str).length() * 2
) and
not possible_reflective_name(getValueText(str)) and
not capitalized_word(str) and
not format_string(str)
)
or
/* Or, an integer with over 32 bits */
exists(IntegerLiteral lit | lit = e |
not exists(lit.getValue()) and
/* Not a set of flags or round number */
not lit.toString().matches("%00%")
)
}
class HardcodedValueSource extends DataFlow::Node {
HardcodedValueSource() { maybeCredential(this.asExpr().getExpr()) }
}
/**
* Gets a regular expression for matching names of locations (variables, parameters, keys) that
* indicate the value being held is a credential.
*/
private string getACredentialRegExp() {
result = "(?i).*pass(wd|word|code|phrase)(?!.*question).*" or
result = "(?i).*(puid|username|userid).*" or
result = "(?i).*(cert)(?!.*(format|name)).*"
}
bindingset[name]
private predicate maybeCredentialName(string name) {
name.regexpMatch(getACredentialRegExp()) and
not name.matches("%file")
}
// Positional parameter
private DataFlow::Node credentialParameter() {
exists(Method m, NamedParameter p |
result.asParameter() = p and
p = m.getAParameter() and
maybeCredentialName(p.getName())
)
}
// Keyword argument
private Expr credentialKeywordArgument() {
exists(MethodCall mc, string argKey |
result = mc.getKeywordArgument(argKey) and
maybeCredentialName(argKey)
)
}
// An equality check against a credential value
private Expr credentialComparison() {
exists(EqualityOperation op, VariableReadAccess vra |
maybeCredentialName(vra.getVariable().getName()) and
(
op.getLeftOperand() = result and
op.getRightOperand() = vra
or
op.getLeftOperand() = vra and op.getRightOperand() = result
)
)
}
private predicate isCredentialSink(DataFlow::Node node) {
node = credentialParameter()
or
node.asExpr().getExpr() = credentialKeywordArgument()
or
node.asExpr().getExpr() = credentialComparison()
}
class CredentialSink extends DataFlow::Node {
CredentialSink() { isCredentialSink(this) }
}
private module HardcodedCredentialsConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(ExprNodes::BinaryOperationCfgNode binop |
(
binop.getLeftOperand() = node1.asExpr() or
binop.getRightOperand() = node1.asExpr()
) and
binop = node2.asExpr() and
// string concatenation
binop.getExpr() instanceof AddExpr
)
}
predicate observeDiffInformedIncrementalMode() { any() }
}
private module HardcodedCredentialsFlow = DataFlow::Global<HardcodedCredentialsConfig>;
import HardcodedCredentialsFlow::PathGraph
from HardcodedCredentialsFlow::PathNode source, HardcodedCredentialsFlow::PathNode sink
where HardcodedCredentialsFlow::flowPath(source, sink)
select source.getNode(), source, sink, "This hardcoded value is $@.", sink.getNode(),
"used as credentials"