Skip to content

Commit a4f3a5a

Browse files
Take into account remote user input in java/non-constant-time-crypto-comparison
1 parent 8e6d227 commit a4f3a5a

File tree

3 files changed

+187
-98
lines changed

3 files changed

+187
-98
lines changed

java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql

Lines changed: 118 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,44 +13,93 @@
1313

1414
import java
1515
import semmle.code.java.dataflow.TaintTracking
16+
import semmle.code.java.dataflow.TaintTracking2
17+
import semmle.code.java.dataflow.FlowSources
1618
import DataFlow::PathGraph
1719

20+
private class UserInputInCryptoOperationConfig extends TaintTracking2::Configuration {
21+
UserInputInCryptoOperationConfig() { this = "UserInputInCryptoOperationConfig" }
22+
23+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
24+
25+
override predicate isSink(DataFlow::Node sink) {
26+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
27+
(
28+
(
29+
m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") or
30+
m.hasQualifiedName("java.security", "Signature", "sign")
31+
) and
32+
ma.getQualifier() = sink.asExpr()
33+
)
34+
)
35+
}
36+
37+
override predicate isAdditionalTaintStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) {
38+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
39+
(
40+
(
41+
m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], ["doFinal", "update"]) or
42+
m.hasQualifiedName("java.security", "Signature", ["sign", "update"])
43+
) and
44+
ma.getAnArgument() = fromNode.asExpr() and
45+
ma.getQualifier() = toNode.asExpr()
46+
)
47+
)
48+
}
49+
}
50+
51+
abstract private class CryptoOperationSource extends DataFlow::Node {
52+
Expr cryptoOperation;
53+
54+
predicate includesUserInput() {
55+
exists(
56+
DataFlow2::PathNode source, DataFlow2::PathNode sink, UserInputInCryptoOperationConfig config
57+
|
58+
config.hasFlowPath(source, sink)
59+
|
60+
sink.getNode().asExpr() = cryptoOperation
61+
)
62+
}
63+
}
64+
1865
/**
1966
* A source that produces a MAC.
2067
*/
21-
private class MacSource extends DataFlow::Node {
68+
private class MacSource extends CryptoOperationSource {
2269
MacSource() {
2370
exists(MethodAccess ma, Method m | m = ma.getMethod() |
2471
m.hasQualifiedName("javax.crypto", "Mac", "doFinal") and
2572
(
2673
m.getReturnType() instanceof Array and ma = this.asExpr()
2774
or
2875
m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr()
29-
)
76+
) and
77+
cryptoOperation = ma.getQualifier()
3078
)
3179
}
3280
}
3381

3482
/**
3583
* A source that produces a signature.
3684
*/
37-
private class SignatureSource extends DataFlow::Node {
85+
private class SignatureSource extends CryptoOperationSource {
3886
SignatureSource() {
3987
exists(MethodAccess ma, Method m | m = ma.getMethod() |
4088
m.hasQualifiedName("java.security", "Signature", "sign") and
4189
(
4290
m.getReturnType() instanceof Array and ma = this.asExpr()
4391
or
4492
m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr()
45-
)
93+
) and
94+
cryptoOperation = ma.getQualifier()
4695
)
4796
}
4897
}
4998

5099
/**
51100
* A source that produces a ciphertext.
52101
*/
53-
private class CiphertextSource extends DataFlow::Node {
102+
private class CiphertextSource extends CryptoOperationSource {
54103
CiphertextSource() {
55104
exists(MethodAccess ma, Method m | m = ma.getMethod() |
56105
m.hasQualifiedName("javax.crypto", "Cipher", "doFinal") and
@@ -61,7 +110,28 @@ private class CiphertextSource extends DataFlow::Node {
61110
or
62111
m.getParameterType(1).(RefType).hasQualifiedName("java.nio", "ByteBuffer") and
63112
ma.getArgument(1) = this.asExpr()
64-
)
113+
) and
114+
cryptoOperation = ma.getQualifier()
115+
)
116+
}
117+
}
118+
119+
private class UserInputComparisonConfig extends TaintTracking2::Configuration {
120+
UserInputComparisonConfig() { this = "UserInputComparisonConfig" }
121+
122+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
123+
124+
override predicate isSink(DataFlow::Node sink) {
125+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
126+
(
127+
m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or
128+
m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) or
129+
m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or
130+
m.hasQualifiedName("java.util", "Objects", "deepEquals") or
131+
m.hasQualifiedName("org.apache.commons.lang3", "StringUtils",
132+
["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"])
133+
) and
134+
[ma.getAnArgument(), ma.getQualifier()] = sink.asExpr()
65135
)
66136
}
67137
}
@@ -70,41 +140,46 @@ private class CiphertextSource extends DataFlow::Node {
70140
* A sink that compares input using a non-constant time algorithm.
71141
*/
72142
private class KnownNonConstantTimeComparisonSink extends DataFlow::Node {
143+
Expr anotherParameter;
144+
73145
KnownNonConstantTimeComparisonSink() {
74146
exists(MethodAccess ma, Method m | m = ma.getMethod() |
75-
m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) and
76-
this.asExpr() = [ma.getQualifier(), ma.getAnArgument()]
77-
or
78-
m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) and
79-
this.asExpr() = [ma.getQualifier(), ma.getAnArgument()]
80-
or
81-
m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) and
82-
ma.getAnArgument() = this.asExpr()
83-
or
84-
m.hasQualifiedName("java.util", "Objects", "deepEquals") and
85-
ma.getAnArgument() = this.asExpr()
86-
or
87-
m.hasQualifiedName("org.apache.commons.lang3", "StringUtils",
88-
["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) and
89-
ma.getAnArgument() = this.asExpr()
147+
(
148+
m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or
149+
m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"])
150+
) and
151+
(
152+
this.asExpr() = ma.getQualifier() and
153+
anotherParameter = ma.getAnArgument() and
154+
not ma.getAnArgument().isCompileTimeConstant()
155+
or
156+
this.asExpr() = ma.getAnArgument() and
157+
anotherParameter = ma.getQualifier() and
158+
not ma.getQualifier().isCompileTimeConstant()
159+
)
160+
)
161+
or
162+
exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
163+
(
164+
m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or
165+
m.hasQualifiedName("java.util", "Objects", "deepEquals") or
166+
m.hasQualifiedName("org.apache.commons.lang3", "StringUtils",
167+
["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"])
168+
) and
169+
ma.getAnArgument() = this.asExpr() and
170+
(
171+
this.asExpr() = ma.getArgument(0) and anotherParameter = ma.getArgument(1)
172+
or
173+
this.asExpr() = ma.getArgument(1) and anotherParameter = ma.getArgument(0)
174+
)
90175
)
91176
}
92-
}
93177

94-
/**
95-
* Holds if `fromNode` to `toNode` is a dataflow step
96-
* that converts a `ByteBuffer` to a byte array and vice versa.
97-
*/
98-
private predicate convertByteBufferStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
99-
exists(MethodAccess ma, Method m | m = ma.getMethod() |
100-
m.hasQualifiedName("java.nio", "ByteBuffer", "array") and
101-
ma.getQualifier() = fromNode.asExpr() and
102-
ma = toNode.asExpr()
103-
or
104-
m.hasQualifiedName("java.nio", "ByteBuffer", "wrap") and
105-
ma.getAnArgument() = fromNode.asExpr() and
106-
ma = toNode.asExpr()
107-
)
178+
predicate includesUserInput() {
179+
exists(UserInputComparisonConfig config |
180+
config.hasFlowTo(DataFlow2::exprNode(anotherParameter))
181+
)
182+
}
108183
}
109184

110185
/**
@@ -114,24 +189,19 @@ private predicate convertByteBufferStep(DataFlow::Node fromNode, DataFlow::Node
114189
private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration {
115190
NonConstantTimeCryptoComparisonConfig() { this = "NonConstantTimeCryptoComparisonConfig" }
116191

117-
override predicate isSource(DataFlow::Node source) {
118-
source instanceof MacSource
119-
or
120-
source instanceof SignatureSource
121-
or
122-
source instanceof CiphertextSource
123-
}
192+
override predicate isSource(DataFlow::Node source) { source instanceof CryptoOperationSource }
124193

125194
override predicate isSink(DataFlow::Node sink) {
126195
sink instanceof KnownNonConstantTimeComparisonSink
127196
}
128-
129-
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
130-
convertByteBufferStep(fromNode, toNode)
131-
}
132197
}
133198

134199
from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoComparisonConfig conf
135-
where conf.hasFlowPath(source, sink)
200+
where
201+
conf.hasFlowPath(source, sink) and
202+
(
203+
source.getNode().(CryptoOperationSource).includesUserInput() or
204+
sink.getNode().(KnownNonConstantTimeComparisonSink).includesUserInput()
205+
)
136206
select sink.getNode(), source, sink,
137207
"Using a non-constant time algorithm for comparing results of a cryptographic operation."

0 commit comments

Comments
 (0)