8
8
*/
9
9
10
10
import java
11
- import semmle.code.java.dataflow.DataFlow3
12
11
import semmle.code.java.dataflow.TaintTracking
13
12
import semmle.code.java.dataflow.TaintTracking2
14
- import semmle.code.java.dataflow.TaintTracking3
15
13
import DataFlow:: PathGraph
16
14
17
15
/** The Java class `java.security.MessageDigest`. */
@@ -44,38 +42,91 @@ class MDUpdateMethod extends Method {
44
42
}
45
43
}
46
44
45
+ /** The hashing method that could taint the input. */
46
+ class MDHashMethodAccess extends MethodAccess {
47
+ MDHashMethodAccess ( ) {
48
+ (
49
+ this .getMethod ( ) instanceof MDDigestMethod or
50
+ this .getMethod ( ) instanceof MDUpdateMethod
51
+ ) and
52
+ this .getNumArgument ( ) != 0
53
+ }
54
+ }
55
+
47
56
/** Gets a regular expression for matching common names of variables that indicate the value being held is a password. */
48
57
string getPasswordRegex ( ) { result = "(?i).*pass(wd|word|code|phrase).*" }
49
58
50
59
/** Finds variables that hold password information judging by their names. */
51
60
class PasswordVarExpr extends Expr {
52
61
PasswordVarExpr ( ) {
53
- exists ( Variable v | this = v .getAnAccess ( ) |
54
- (
55
- v .getName ( ) .toLowerCase ( ) .regexpMatch ( getPasswordRegex ( ) ) and
56
- not v .getName ( ) .toLowerCase ( ) .matches ( "%hash%" ) // Exclude variable names such as `passwordHash` since their values were already hashed
57
- )
58
- )
62
+ this .( VarAccess ) .getVariable ( ) .getName ( ) .toLowerCase ( ) .regexpMatch ( getPasswordRegex ( ) ) and
63
+ not this .( VarAccess ) .getVariable ( ) .getName ( ) .toLowerCase ( ) .matches ( "%hash%" ) // Exclude variable names such as `passwordHash` since their values were already hashed
59
64
}
60
65
}
61
66
62
- /** 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. */
63
- class PasswordHashConfiguration extends TaintTracking3:: Configuration {
64
- PasswordHashConfiguration ( ) { this = "PasswordHashConfiguration" }
65
-
66
- override predicate isSource ( DataFlow3:: Node source ) { source .asExpr ( ) instanceof PasswordVarExpr }
67
-
68
- override predicate isSink ( DataFlow3:: Node sink ) {
69
- exists (
70
- MethodAccess ma // invoke `md.update(password)` without the call of `md.update(digest)`
71
- |
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
- )
67
+ /** Holds if `Expr` e is an operand of `AddExpr`. */
68
+ predicate hasAddExpr ( AddExpr ae , Expr e ) {
69
+ ae .getAnOperand ( ) = e or
70
+ hasAddExpr ( ae .getAnOperand ( ) , e )
71
+ }
72
+
73
+ /** Holds if `MethodAccess` ma has a flow to another `MDHashMethodAccess` call. */
74
+ predicate hasAnotherHashCall ( MethodAccess ma ) {
75
+ exists ( MethodAccess ma2 , DataFlow2:: Node node1 , DataFlow2:: Node node2 |
76
+ ma2 instanceof MDHashMethodAccess and
77
+ ma2 != ma and
78
+ node1 .asExpr ( ) = ma .getAChildExpr ( ) and
79
+ node2 .asExpr ( ) = ma2 .getAChildExpr ( ) and
80
+ (
81
+ TaintTracking2:: localTaint ( node1 , node2 ) or
82
+ TaintTracking2:: localTaint ( node2 , node1 )
83
+ )
84
+ )
85
+ }
86
+
87
+ /** Holds if `MethodAccess` ma is a hashing call without a sibling node making another hashing call. */
88
+ predicate isSingleHashMethodCall ( MethodAccess ma ) {
89
+ (
90
+ ma instanceof MDHashMethodAccess and
91
+ not hasAnotherHashCall ( ma )
92
+ )
93
+ }
94
+
95
+ /** Holds if `MethodAccess` ma is invoked by `MethodAccess` ma2 either directly or indirectly. */
96
+ predicate hasParentCall ( MethodAccess ma2 , MethodAccess ma ) {
97
+ ma .getCaller ( ) = ma2 .getMethod ( ) and
98
+ not ma2 instanceof MDHashMethodAccess
99
+ or
100
+ exists ( MethodAccess ma3 |
101
+ ma .getCaller ( ) = ma3 .getMethod ( ) and
102
+ not ma3 instanceof MDHashMethodAccess and
103
+ hasParentCall ( ma2 , ma3 )
104
+ )
105
+ }
106
+
107
+ /** Holds if `MethodAccess` is a single hashing call. */
108
+ predicate isSink ( MethodAccess ma ) {
109
+ isSingleHashMethodCall ( ma ) and
110
+ not exists ( MethodAccess ma2 | hasParentCall ( ma2 , ma ) )
111
+ }
112
+
113
+ /** Sink of hashing calls. */
114
+ class HashWithoutSaltSink extends DataFlow:: ExprNode {
115
+ HashWithoutSaltSink ( ) {
116
+ exists ( MethodAccess ma |
117
+ this .asExpr ( ) = ma .getAnArgument ( ) and
118
+ isSink ( ma )
77
119
)
78
120
}
121
+ }
122
+
123
+ /** 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. */
124
+ class HashWithoutSaltConfiguration extends TaintTracking:: Configuration {
125
+ HashWithoutSaltConfiguration ( ) { this = "HashWithoutSaltConfiguration" }
126
+
127
+ override predicate isSource ( DataFlow:: Node source ) { source .asExpr ( ) instanceof PasswordVarExpr }
128
+
129
+ override predicate isSink ( DataFlow:: Node sink ) { sink instanceof HashWithoutSaltSink }
79
130
80
131
/**
81
132
* Holds if a password is concatenated with a salt then hashed together through the call `System.arraycopy(password.getBytes(), ...)`, for example,
@@ -84,60 +135,26 @@ class PasswordHashConfiguration extends TaintTracking3::Configuration {
84
135
* `byte[] messageDigest = md.digest(allBytes);`
85
136
* Or the password is concatenated with a salt as a string.
86
137
*/
87
- override predicate isSanitizer ( DataFlow3 :: Node node ) {
138
+ override predicate isSanitizer ( DataFlow :: Node node ) {
88
139
exists ( MethodAccess ma |
89
140
ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.lang" , "System" ) and
90
141
ma .getMethod ( ) .hasName ( "arraycopy" ) and
91
142
ma .getArgument ( 0 ) = node .asExpr ( )
92
143
) // System.arraycopy(password.getBytes(), ...)
93
144
or
94
- exists ( AddExpr e | node .asExpr ( ) = e .getAnOperand ( ) ) // password+salt
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 |
107
- (
108
- ma .getMethod ( ) instanceof MDUpdateMethod or
109
- ma .getMethod ( ) instanceof MDDigestMethod
110
- ) and
111
- exists ( PasswordHashConfiguration cc | cc .hasFlowToExpr ( ma .getAnArgument ( ) ) ) and
112
- sink .asExpr ( ) = ma .getQualifier ( )
113
- )
114
- }
115
- }
116
-
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 ) {
145
+ exists ( AddExpr e | hasAddExpr ( e , node .asExpr ( ) ) ) // password+salt
146
+ or
147
+ exists ( ConditionalExpr ce | ce = node .asExpr ( ) ) // useSalt?password+":"+salt:password
148
+ or
125
149
exists ( MethodAccess ma |
126
- ma .getMethod ( ) instanceof MDDigestMethod and // md.digest(password)
127
- sink .asExpr ( ) = ma .getQualifier ( )
150
+ ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.lang" , "StringBuilder" ) and
151
+ ma .getMethod ( ) .hasName ( "append" ) and
152
+ ma .getArgument ( 0 ) = node .asExpr ( ) // stringBuilder.append(password).append(salt)
128
153
)
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 ) {
154
+ or
133
155
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 ( ) ) )
156
+ ma .getQualifier ( ) .( VarAccess ) .getVariable ( ) .getType ( ) instanceof Interface and
157
+ ma .getAnArgument ( ) = node .asExpr ( ) // Method access of interface type variables requires runtime determination
141
158
)
142
159
}
143
160
}
0 commit comments