88 */
99
1010import java
11- import semmle.code.java.dataflow.DataFlow3
1211import semmle.code.java.dataflow.TaintTracking
1312import semmle.code.java.dataflow.TaintTracking2
14- import semmle.code.java.dataflow.TaintTracking3
1513import DataFlow:: PathGraph
1614
1715/** The Java class `java.security.MessageDigest`. */
@@ -44,38 +42,91 @@ class MDUpdateMethod extends Method {
4442 }
4543}
4644
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+
4756/** Gets a regular expression for matching common names of variables that indicate the value being held is a password. */
4857string getPasswordRegex ( ) { result = "(?i).*pass(wd|word|code|phrase).*" }
4958
5059/** Finds variables that hold password information judging by their names. */
5160class PasswordVarExpr extends Expr {
5261 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
5964 }
6065}
6166
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 )
77119 )
78120 }
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 }
79130
80131 /**
81132 * 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 {
84135 * `byte[] messageDigest = md.digest(allBytes);`
85136 * Or the password is concatenated with a salt as a string.
86137 */
87- override predicate isSanitizer ( DataFlow3 :: Node node ) {
138+ override predicate isSanitizer ( DataFlow :: Node node ) {
88139 exists ( MethodAccess ma |
89140 ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.lang" , "System" ) and
90141 ma .getMethod ( ) .hasName ( "arraycopy" ) and
91142 ma .getArgument ( 0 ) = node .asExpr ( )
92143 ) // System.arraycopy(password.getBytes(), ...)
93144 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
125149 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)
128153 )
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
133155 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
141158 )
142159 }
143160}
0 commit comments