@@ -19,15 +19,15 @@ import semmle.code.java.dataflow.NullGuards
19
19
import DataFlow:: PathGraph
20
20
21
21
/**
22
- * Holds if `ma` is a call to a method that checks exact match of string, probably a whitelisted one .
22
+ * Holds if `ma` is a call to a method that checks exact match of string.
23
23
*/
24
24
predicate isExactStringPathMatch ( MethodAccess ma ) {
25
25
ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
26
26
ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ]
27
27
}
28
28
29
29
/**
30
- * Holds if `ma` is a call to a method that checks a path string, probably a whitelisted one .
30
+ * Holds if `ma` is a call to a method that checks a path string.
31
31
*/
32
32
predicate isStringPathMatch ( MethodAccess ma ) {
33
33
ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
@@ -36,46 +36,44 @@ predicate isStringPathMatch(MethodAccess ma) {
36
36
}
37
37
38
38
/**
39
- * Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a path, probably
40
- * a whitelisted one.
39
+ * Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a path.
41
40
*/
42
41
predicate isFilePathMatch ( MethodAccess ma ) {
43
42
ma .getMethod ( ) .getDeclaringType ( ) instanceof TypePath and
44
43
ma .getMethod ( ) .getName ( ) = "startsWith"
45
44
}
46
45
47
46
/**
48
- * Holds if `ma` is a call to a method that checks an input doesn't match using the `!`
49
- * logical negation expression.
47
+ * Holds if `ma` protects against path traversal, by either:
48
+ * * looking for the literal `..`
49
+ * * performing path normalization
50
50
*/
51
- predicate checkNoPathMatch ( MethodAccess ma ) {
52
- exists ( LogNotExpr lne |
53
- ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
54
- lne .getExpr ( ) = ma
55
- )
56
- }
57
-
58
- /**
59
- * Holds if `ma` is a call to a method that checks special characters `..` used in path traversal.
60
- */
61
- predicate isPathTraversalCheck ( MethodAccess ma ) {
51
+ predicate isPathTraversalCheck ( MethodAccess ma , Expr checked ) {
62
52
ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
63
53
ma .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
64
- ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
54
+ ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".." and
55
+ ma .( Guard ) .controls ( checked .getBasicBlock ( ) , false )
56
+ or
57
+ ma .getMethod ( ) instanceof PathNormalizeMethod and
58
+ checked = ma
65
59
}
66
60
67
61
/**
68
- * Holds if `ma` is a call to a method that decodes a URL string or check URL encoding.
62
+ * Holds if `ma` protects against double URL encoding, by either:
63
+ * * looking for the literal `%`
64
+ * * performing URL decoding
69
65
*/
70
- predicate isPathDecoding ( MethodAccess ma ) {
66
+ predicate isURLEncodingCheck ( MethodAccess ma , Expr checked ) {
71
67
// Search the special character `%` used in url encoding
72
68
ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
73
69
ma .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
74
- ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%"
70
+ ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%" and
71
+ ma .( Guard ) .controls ( checked .getBasicBlock ( ) , false )
75
72
or
76
73
// Call to `URLDecoder` assuming the implementation handles double encoding correctly
77
74
ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.net" , "URLDecoder" ) and
78
- ma .getMethod ( ) .hasName ( "decode" )
75
+ ma .getMethod ( ) .hasName ( "decode" ) and
76
+ checked = ma
79
77
}
80
78
81
79
/** The Java method `normalize` of `java.nio.file.Path`. */
@@ -86,90 +84,54 @@ class PathNormalizeMethod extends Method {
86
84
}
87
85
}
88
86
87
+ private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
88
+ word .getStringValue ( ) .matches ( [ "%WEB-INF%" , "%META-INF%" , "%..%" ] )
89
+ }
90
+
91
+ private predicate isAllowListCheck ( MethodAccess ma ) {
92
+ ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
93
+ not isDisallowedWord ( ma .getAnArgument ( ) )
94
+ }
95
+
96
+ private predicate isDisallowListCheck ( MethodAccess ma ) {
97
+ ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
98
+ isDisallowedWord ( ma .getAnArgument ( ) )
99
+ }
100
+
89
101
/**
90
- * Sanitizer to check the following scenarios in a web application :
102
+ * A guard that checks a path with the following methods :
91
103
* 1. Exact string match
92
- * 2. String startsWith or match check with path traversal validation
93
- * 3. String not startsWith or not match check with decoding processing
94
- * 4. java.nio.file.Path startsWith check having path normalization
104
+ * 2. Path matches allowed values (needs to protect against path traversal)
105
+ * 3. Path matches disallowed values (needs to protect against URL encoding)
95
106
*/
96
107
private class PathMatchGuard extends DataFlow:: BarrierGuard {
97
108
PathMatchGuard ( ) {
98
- isExactStringPathMatch ( this )
99
- or
100
- isStringPathMatch ( this ) and
101
- not checkNoPathMatch ( this ) and
102
- exists ( MethodAccess tma |
103
- isPathTraversalCheck ( tma ) and
104
- DataFlow:: localExprFlow ( this .( MethodAccess ) .getQualifier ( ) , tma .getQualifier ( ) )
105
- )
106
- or
107
- checkNoPathMatch ( this ) and
108
- exists ( MethodAccess dma |
109
- isPathDecoding ( dma ) and
110
- DataFlow:: localExprFlow ( dma , this .( MethodAccess ) .getQualifier ( ) )
111
- )
112
- or
113
- isFilePathMatch ( this ) and
114
- exists ( MethodAccess pma |
115
- pma .getMethod ( ) instanceof PathNormalizeMethod and
116
- DataFlow:: localExprFlow ( pma , this .( MethodAccess ) .getQualifier ( ) )
117
- )
109
+ isExactStringPathMatch ( this ) or isStringPathMatch ( this ) or isFilePathMatch ( this )
118
110
}
119
111
120
112
override predicate checks ( Expr e , boolean branch ) {
121
113
e = this .( MethodAccess ) .getQualifier ( ) and
122
114
(
123
- branch = true and not checkNoPathMatch ( this )
115
+ isExactStringPathMatch ( this ) and
116
+ branch = true
124
117
or
125
- branch = false and checkNoPathMatch ( this )
126
- )
127
- }
128
- }
129
-
130
- /**
131
- * Holds if `ma` is a call to a method that checks string content, which means an input string is not
132
- * blindly trusted and helps to reduce FPs.
133
- */
134
- predicate checkStringContent ( MethodAccess ma , Expr expr ) {
135
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
136
- ma .getMethod ( )
137
- .hasName ( [
138
- "charAt" , "getBytes" , "getChars" , "length" , "replace" , "replaceAll" , "replaceFirst" ,
139
- "substring"
140
- ] ) and
141
- expr = ma .getQualifier ( )
142
- or
143
- (
144
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeStringBuffer or
145
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeStringBuilder
146
- ) and
147
- expr = ma .getAnArgument ( )
148
- }
149
-
150
- private class StringOperationSanitizer extends DataFlow:: Node {
151
- StringOperationSanitizer ( ) { exists ( MethodAccess ma | checkStringContent ( ma , this .asExpr ( ) ) ) }
152
- }
153
-
154
- private class NullOrEmptyCheckGuard extends DataFlow:: BarrierGuard {
155
- NullOrEmptyCheckGuard ( ) {
156
- this = nullGuard ( _, _, _)
157
- or
158
- exists ( MethodAccess ma |
159
- cb .getCondition ( ) = ma and
160
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
161
- ma .getMethod ( ) .hasName ( "equals" ) and
162
- ma .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "" and
163
- this = ma
118
+ isAllowListCheck ( this ) and
119
+ exists ( MethodAccess ma , Expr checked | isPathTraversalCheck ( ma , checked ) |
120
+ DataFlow:: localExprFlow ( checked , e )
121
+ or
122
+ ma .getParent * ( ) .( BinaryExpr ) = this .( MethodAccess ) .getParent * ( )
123
+ ) and
124
+ branch = true
125
+ or
126
+ isDisallowListCheck ( this ) and
127
+ exists ( MethodAccess ma , Expr checked | isURLEncodingCheck ( ma , checked ) |
128
+ DataFlow:: localExprFlow ( checked , e )
129
+ or
130
+ ma .getParent * ( ) .( BinaryExpr ) = this .( MethodAccess ) .getParent * ( )
131
+ ) and
132
+ branch = false
164
133
)
165
134
}
166
-
167
- override predicate checks ( Expr e , boolean branch ) {
168
- exists ( SsaVariable ssa | this = nullGuard ( ssa , branch , true ) and e = ssa .getAFirstUse ( ) )
169
- or
170
- e = this .( MethodAccess ) .getQualifier ( ) and
171
- branch = true
172
- }
173
135
}
174
136
175
137
class UnsafeUrlForwardFlowConfig extends TaintTracking:: Configuration {
@@ -189,14 +151,10 @@ class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
189
151
190
152
override predicate isSink ( DataFlow:: Node sink ) { sink instanceof UnsafeUrlForwardSink }
191
153
192
- override predicate isSanitizer ( DataFlow:: Node node ) {
193
- node instanceof UnsafeUrlForwardSanitizer or
194
- node instanceof StringOperationSanitizer
195
- }
154
+ override predicate isSanitizer ( DataFlow:: Node node ) { node instanceof UnsafeUrlForwardSanitizer }
196
155
197
156
override predicate isSanitizerGuard ( DataFlow:: BarrierGuard guard ) {
198
- guard instanceof PathMatchGuard or
199
- guard instanceof NullOrEmptyCheckGuard
157
+ guard instanceof PathMatchGuard
200
158
}
201
159
202
160
override DataFlow:: FlowFeature getAFeature ( ) {
0 commit comments