Skip to content

Commit ff1ed3a

Browse files
committed
Revamp the query to use three configurations to detect password hash without salt
1 parent b9809b0 commit ff1ed3a

File tree

4 files changed

+194
-43
lines changed

4 files changed

+194
-43
lines changed

java/ql/src/experimental/Security/CWE/CWE-759/HashWithoutSalt.ql

Lines changed: 67 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,26 @@
88
*/
99

1010
import java
11+
import semmle.code.java.dataflow.DataFlow3
1112
import semmle.code.java.dataflow.TaintTracking
13+
import semmle.code.java.dataflow.TaintTracking2
14+
import semmle.code.java.dataflow.TaintTracking3
1215
import DataFlow::PathGraph
1316

1417
/** The Java class `java.security.MessageDigest`. */
1518
class MessageDigest extends RefType {
1619
MessageDigest() { this.hasQualifiedName("java.security", "MessageDigest") }
1720
}
1821

22+
class MDConstructor extends StaticMethodAccess {
23+
MDConstructor() {
24+
exists(Method m | m = this.getMethod() |
25+
m.getDeclaringType() instanceof MessageDigest and
26+
m.hasName("getInstance")
27+
)
28+
}
29+
}
30+
1931
/** The method `digest()` declared in `java.security.MessageDigest`. */
2032
class MDDigestMethod extends Method {
2133
MDDigestMethod() {
@@ -48,24 +60,20 @@ class PasswordVarExpr extends Expr {
4860
}
4961

5062
/** Taint configuration tracking flow from an expression whose name suggests it holds password data to a method call that generates a hash without a salt. */
51-
class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
52-
HashWithoutSaltConfiguration() { this = "HashWithoutSaltConfiguration" }
63+
class PasswordHashConfiguration extends TaintTracking3::Configuration {
64+
PasswordHashConfiguration() { this = "PasswordHashConfiguration" }
5365

54-
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof PasswordVarExpr }
66+
override predicate isSource(DataFlow3::Node source) { source.asExpr() instanceof PasswordVarExpr }
5567

56-
override predicate isSink(DataFlow::Node sink) {
68+
override predicate isSink(DataFlow3::Node sink) {
5769
exists(
58-
MethodAccess mua // invoke `md.update(password)` without the call of `md.update(digest)`
70+
MethodAccess ma // invoke `md.update(password)` without the call of `md.update(digest)`
5971
|
60-
sink.asExpr() = mua.getArgument(0) and
61-
mua.getMethod() instanceof MDUpdateMethod // md.update(password)
62-
)
63-
or
64-
// invoke `md.digest(password)` without another call of `md.update(salt)`
65-
exists(MethodAccess mda |
66-
sink.asExpr() = mda.getArgument(0) and
67-
mda.getMethod() instanceof MDDigestMethod and // md.digest(password)
68-
mda.getNumArgument() = 1
72+
sink.asExpr() = ma.getArgument(0) and
73+
(
74+
ma.getMethod() instanceof MDUpdateMethod or // md.update(password)
75+
ma.getMethod() instanceof MDDigestMethod // md.digest(password)
76+
)
6977
)
7078
}
7179

@@ -76,48 +84,64 @@ class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
7684
* `byte[] messageDigest = md.digest(allBytes);`
7785
* Or the password is concatenated with a salt as a string.
7886
*/
79-
override predicate isSanitizer(DataFlow::Node node) {
87+
override predicate isSanitizer(DataFlow3::Node node) {
8088
exists(MethodAccess ma |
8189
ma.getMethod().getDeclaringType().hasQualifiedName("java.lang", "System") and
8290
ma.getMethod().hasName("arraycopy") and
8391
ma.getArgument(0) = node.asExpr()
8492
) // System.arraycopy(password.getBytes(), ...)
8593
or
8694
exists(AddExpr e | node.asExpr() = e.getAnOperand()) // password+salt
87-
or
88-
exists(MethodAccess mua, MethodAccess ma |
89-
ma.getArgument(0) = node.asExpr() and // Detect wrapper methods that invoke `md.update(salt)`
90-
ma != mua and
95+
}
96+
}
97+
98+
class PasswordDigestConfiguration extends TaintTracking2::Configuration {
99+
PasswordDigestConfiguration() { this = "PasswordDigestConfiguration" }
100+
101+
override predicate isSource(DataFlow2::Node source) {
102+
exists(MDConstructor mc | source.asExpr() = mc)
103+
}
104+
105+
override predicate isSink(DataFlow2::Node sink) {
106+
exists(MethodAccess ma |
91107
(
92-
ma.getQualifier().getType() instanceof Interface
93-
or
94-
mua.getQualifier().(VarAccess).getVariable().getAnAccess() = ma.getQualifier()
95-
or
96-
mua.getAnArgument().(VarAccess).getVariable().getAnAccess() = ma.getQualifier()
97-
or
98-
mua.getQualifier().(VarAccess).getVariable().getAnAccess() = ma.getAnArgument()
99-
or
100-
mua.getArgument(0).(VarAccess).getVariable().getAnAccess() = ma.getAnArgument()
108+
ma.getMethod() instanceof MDUpdateMethod or
109+
ma.getMethod() instanceof MDDigestMethod
101110
) and
102-
isMDUpdateCall(mua.getMethod())
111+
exists(PasswordHashConfiguration cc | cc.hasFlowToExpr(ma.getAnArgument())) and
112+
sink.asExpr() = ma.getQualifier()
103113
)
104114
}
105115
}
106116

107-
/** Holds if a method invokes `md.update(salt)`. */
108-
predicate isMDUpdateCall(Callable caller) {
109-
caller instanceof MDUpdateMethod
110-
or
111-
exists(Callable callee |
112-
caller.polyCalls(callee) and
113-
(
114-
callee instanceof MDUpdateMethod or
115-
isMDUpdateCall(callee)
117+
class HashWithoutSaltConfiguration extends TaintTracking::Configuration {
118+
HashWithoutSaltConfiguration() { this = "HashWithoutSaltConfiguration" }
119+
120+
override predicate isSource(DataFlow::Node source) {
121+
exists(PasswordDigestConfiguration pc | pc.hasFlow(source, _))
122+
}
123+
124+
override predicate isSink(DataFlow::Node sink) {
125+
exists(MethodAccess ma |
126+
ma.getMethod() instanceof MDDigestMethod and // md.digest(password)
127+
sink.asExpr() = ma.getQualifier()
128+
)
129+
}
130+
131+
/** Holds if `md.update` or `md.digest` calls integrate something other than the password, perhaps a salt. */
132+
override predicate isSanitizer(DataFlow::Node node) {
133+
exists(MethodAccess ma |
134+
(
135+
ma.getMethod() instanceof MDUpdateMethod
136+
or
137+
ma.getMethod() instanceof MDDigestMethod and ma.getNumArgument() != 0
138+
) and
139+
node.asExpr() = ma.getQualifier() and
140+
not exists(PasswordHashConfiguration cc | cc.hasFlowToExpr(ma.getAnArgument()))
116141
)
117-
)
142+
}
118143
}
119144

120-
from DataFlow::PathNode source, DataFlow::PathNode sink, HashWithoutSaltConfiguration c
121-
where c.hasFlowPath(source, sink)
122-
select sink.getNode(), source, sink, "$@ is hashed without a salt.", source.getNode(),
123-
"The password"
145+
from DataFlow::PathNode source, DataFlow::PathNode sink, HashWithoutSaltConfiguration cc
146+
where cc.hasFlowPath(source, sink)
147+
select sink, source, sink, "$@ is hashed without a salt.", source, "The password"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Provides classes for performing local (intra-procedural) and
3+
* global (inter-procedural) taint-tracking analyses.
4+
*/
5+
module TaintTracking3 {
6+
import semmle.code.java.dataflow.internal.tainttracking3.TaintTrackingImpl
7+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Provides an implementation of global (interprocedural) taint tracking.
3+
* This file re-exports the local (intraprocedural) taint-tracking analysis
4+
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
5+
* exposed through the `Configuration` class. For some languages, this file
6+
* exists in several identical copies, allowing queries to use multiple
7+
* `Configuration` classes that depend on each other without introducing
8+
* mutual recursion among those configurations.
9+
*/
10+
11+
import TaintTrackingParameter::Public
12+
private import TaintTrackingParameter::Private
13+
14+
/**
15+
* A configuration of interprocedural taint tracking analysis. This defines
16+
* sources, sinks, and any other configurable aspect of the analysis. Each
17+
* use of the taint tracking library must define its own unique extension of
18+
* this abstract class.
19+
*
20+
* A taint-tracking configuration is a special data flow configuration
21+
* (`DataFlow::Configuration`) that allows for flow through nodes that do not
22+
* necessarily preserve values but are still relevant from a taint tracking
23+
* perspective. (For example, string concatenation, where one of the operands
24+
* is tainted.)
25+
*
26+
* To create a configuration, extend this class with a subclass whose
27+
* characteristic predicate is a unique singleton string. For example, write
28+
*
29+
* ```ql
30+
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
31+
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
32+
* // Override `isSource` and `isSink`.
33+
* // Optionally override `isSanitizer`.
34+
* // Optionally override `isSanitizerIn`.
35+
* // Optionally override `isSanitizerOut`.
36+
* // Optionally override `isSanitizerGuard`.
37+
* // Optionally override `isAdditionalTaintStep`.
38+
* }
39+
* ```
40+
*
41+
* Then, to query whether there is flow between some `source` and `sink`,
42+
* write
43+
*
44+
* ```ql
45+
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
46+
* ```
47+
*
48+
* Multiple configurations can coexist, but it is unsupported to depend on
49+
* another `TaintTracking::Configuration` or a `DataFlow::Configuration` in the
50+
* overridden predicates that define sources, sinks, or additional steps.
51+
* Instead, the dependency should go to a `TaintTracking2::Configuration` or a
52+
* `DataFlow2::Configuration`, `DataFlow3::Configuration`, etc.
53+
*/
54+
abstract class Configuration extends DataFlow::Configuration {
55+
bindingset[this]
56+
Configuration() { any() }
57+
58+
/**
59+
* Holds if `source` is a relevant taint source.
60+
*
61+
* The smaller this predicate is, the faster `hasFlow()` will converge.
62+
*/
63+
// overridden to provide taint-tracking specific qldoc
64+
abstract override predicate isSource(DataFlow::Node source);
65+
66+
/**
67+
* Holds if `sink` is a relevant taint sink.
68+
*
69+
* The smaller this predicate is, the faster `hasFlow()` will converge.
70+
*/
71+
// overridden to provide taint-tracking specific qldoc
72+
abstract override predicate isSink(DataFlow::Node sink);
73+
74+
/** Holds if the node `node` is a taint sanitizer. */
75+
predicate isSanitizer(DataFlow::Node node) { none() }
76+
77+
final override predicate isBarrier(DataFlow::Node node) {
78+
isSanitizer(node) or
79+
defaultTaintSanitizer(node)
80+
}
81+
82+
/** Holds if taint propagation into `node` is prohibited. */
83+
predicate isSanitizerIn(DataFlow::Node node) { none() }
84+
85+
final override predicate isBarrierIn(DataFlow::Node node) { isSanitizerIn(node) }
86+
87+
/** Holds if taint propagation out of `node` is prohibited. */
88+
predicate isSanitizerOut(DataFlow::Node node) { none() }
89+
90+
final override predicate isBarrierOut(DataFlow::Node node) { isSanitizerOut(node) }
91+
92+
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
93+
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
94+
95+
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) { isSanitizerGuard(guard) }
96+
97+
/**
98+
* Holds if the additional taint propagation step from `node1` to `node2`
99+
* must be taken into account in the analysis.
100+
*/
101+
predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
102+
103+
final override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
104+
isAdditionalTaintStep(node1, node2) or
105+
defaultAdditionalTaintStep(node1, node2)
106+
}
107+
108+
/**
109+
* Holds if taint may flow from `source` to `sink` for this configuration.
110+
*/
111+
// overridden to provide taint-tracking specific qldoc
112+
override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
113+
super.hasFlow(source, sink)
114+
}
115+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import semmle.code.java.dataflow.internal.TaintTrackingUtil as Public
2+
3+
module Private {
4+
import semmle.code.java.dataflow.DataFlow3::DataFlow3 as DataFlow
5+
}

0 commit comments

Comments
 (0)