@@ -5,6 +5,8 @@ private import semmle.code.java.controlflow.Guards
5
5
private import semmle.code.java.dataflow.ExternalFlow
6
6
private import semmle.code.java.dataflow.FlowSources
7
7
private import semmle.code.java.dataflow.SSA
8
+ private import semmle.code.java.frameworks.kotlin.IO
9
+ private import semmle.code.java.frameworks.kotlin.Text
8
10
9
11
/** A sanitizer that protects against path injection vulnerabilities. */
10
12
abstract class PathInjectionSanitizer extends DataFlow:: Node { }
@@ -47,16 +49,25 @@ private module ValidationMethod<DataFlow::guardChecksSig/3 validationGuard> {
47
49
*/
48
50
private predicate exactPathMatchGuard ( Guard g , Expr e , boolean branch ) {
49
51
exists ( MethodAccess ma , RefType t |
50
- t instanceof TypeString or
51
- t instanceof TypeUri or
52
- t instanceof TypePath or
53
- t instanceof TypeFile or
54
- t .hasQualifiedName ( "android.net" , "Uri" )
52
+ (
53
+ t instanceof TypeString or
54
+ t instanceof TypeUri or
55
+ t instanceof TypePath or
56
+ t instanceof TypeFile or
57
+ t .hasQualifiedName ( "android.net" , "Uri" )
58
+ ) and
59
+ e = ma .getQualifier ( ) .getUnderlyingExpr ( )
60
+ or
61
+ (
62
+ ma .getMethod ( ) .getDeclaringType ( ) instanceof StringsKt
63
+ or
64
+ ma .getMethod ( ) .getDeclaringType ( ) instanceof FilesKt
65
+ ) and
66
+ e = ma .getArgument ( 0 ) .getUnderlyingExpr ( )
55
67
|
56
68
ma .getMethod ( ) .getDeclaringType ( ) = t and
57
69
ma = g and
58
- ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ] and
59
- e = ma .getQualifier ( ) and
70
+ ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ] + [ "" , "$default" ] and
60
71
branch = true
61
72
)
62
73
}
@@ -82,12 +93,18 @@ private predicate localTaintFlowToPathGuard(Expr e, PathGuard g) {
82
93
}
83
94
84
95
private class AllowedPrefixGuard extends PathGuard instanceof MethodAccess {
96
+ Expr checkedExpr ;
97
+
85
98
AllowedPrefixGuard ( ) {
86
- ( isStringPrefixMatch ( this ) or isPathPrefixMatch ( this ) ) and
99
+ (
100
+ isStringPrefixMatch ( this , checkedExpr )
101
+ or
102
+ isPathPrefixMatch ( this , checkedExpr )
103
+ ) and
87
104
not isDisallowedWord ( super .getAnArgument ( ) )
88
105
}
89
106
90
- override Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
107
+ override Expr getCheckedExpr ( ) { result = checkedExpr }
91
108
}
92
109
93
110
/**
@@ -149,12 +166,18 @@ private class DotDotCheckSanitizer extends PathInjectionSanitizer {
149
166
}
150
167
151
168
private class BlockListGuard extends PathGuard instanceof MethodAccess {
169
+ Expr checkedExpr ;
170
+
152
171
BlockListGuard ( ) {
153
- ( isStringPartialMatch ( this ) or isPathPrefixMatch ( this ) ) and
172
+ (
173
+ isStringPartialMatch ( this , checkedExpr )
174
+ or
175
+ isPathPrefixMatch ( this , checkedExpr )
176
+ ) and
154
177
isDisallowedWord ( super .getAnArgument ( ) )
155
178
}
156
179
157
- override Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
180
+ override Expr getCheckedExpr ( ) { result = checkedExpr }
158
181
}
159
182
160
183
/**
@@ -188,70 +211,109 @@ private class BlockListSanitizer extends PathInjectionSanitizer {
188
211
}
189
212
}
190
213
191
- private predicate isStringPrefixMatch ( MethodAccess ma ) {
192
- exists ( Method m | m = ma .getMethod ( ) and m .getDeclaringType ( ) instanceof TypeString |
193
- m .hasName ( "startsWith" )
194
- or
195
- m .hasName ( "regionMatches" ) and
196
- ma .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getIntValue ( ) = 0
214
+ private class ConstantOrRegex extends Expr {
215
+ ConstantOrRegex ( ) {
216
+ this instanceof CompileTimeConstantExpr or
217
+ this instanceof KtToRegex
218
+ }
219
+
220
+ string getStringValue ( ) {
221
+ result = this .( CompileTimeConstantExpr ) .getStringValue ( ) or
222
+ result = this .( KtToRegex ) .getExpressionString ( )
223
+ }
224
+ }
225
+
226
+ private predicate isStringPrefixMatch ( MethodAccess ma , Expr checkedExpr ) {
227
+ exists ( Method m |
228
+ m = ma .getMethod ( ) and
229
+ (
230
+ m .getDeclaringType ( ) instanceof TypeString and
231
+ checkedExpr = ma .getQualifier ( ) .getUnderlyingExpr ( )
232
+ or
233
+ m .getDeclaringType ( ) instanceof StringsKt and
234
+ checkedExpr = ma .getArgument ( 0 ) .getUnderlyingExpr ( )
235
+ )
236
+ |
237
+ m .hasName ( "startsWith" + [ "" , "$default" ] )
197
238
or
198
- m .hasName ( "matches" ) and
199
- not ma .getArgument ( 0 ) .( CompileTimeConstantExpr ) .getStringValue ( ) .matches ( ".*%" )
239
+ exists ( int argPos |
240
+ m .getDeclaringType ( ) instanceof TypeString and argPos = 0
241
+ or
242
+ m .getDeclaringType ( ) instanceof StringsKt and argPos = 1
243
+ |
244
+ m .hasName ( "regionMatches" + [ "" , "$default" ] ) and
245
+ ma .getArgument ( argPos ) .( CompileTimeConstantExpr ) .getIntValue ( ) = 0
246
+ or
247
+ m .hasName ( "matches" ) and
248
+ not ma .getArgument ( argPos ) .( ConstantOrRegex ) .getStringValue ( ) .matches ( ".*%" )
249
+ )
200
250
)
201
251
}
202
252
203
253
/**
204
- * Holds if `ma` is a call to a method that checks a partial string match.
254
+ * Holds if `ma` is a call to a method that checks a partial string match on `checkedExpr` .
205
255
*/
206
- private predicate isStringPartialMatch ( MethodAccess ma ) {
207
- isStringPrefixMatch ( ma )
256
+ private predicate isStringPartialMatch ( MethodAccess ma , Expr checkedExpr ) {
257
+ isStringPrefixMatch ( ma , checkedExpr )
208
258
or
209
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
210
- ma .getMethod ( ) .hasName ( [ "contains" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ] )
259
+ (
260
+ ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
261
+ checkedExpr = ma .getQualifier ( ) .getUnderlyingExpr ( )
262
+ or
263
+ ma .getMethod ( ) .getDeclaringType ( ) instanceof StringsKt and
264
+ checkedExpr = ma .getArgument ( 0 ) .getUnderlyingExpr ( )
265
+ ) and
266
+ ma .getMethod ( )
267
+ .hasName ( [ "contains" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ] + [ "" , "$default" ] )
211
268
}
212
269
213
270
/**
214
- * Holds if `ma` is a call to a method that checks whether a path starts with a prefix.
271
+ * Holds if `ma` is a call to a method that checks whether `checkedExpr` starts with a prefix.
215
272
*/
216
- private predicate isPathPrefixMatch ( MethodAccess ma ) {
273
+ private predicate isPathPrefixMatch ( MethodAccess ma , Expr checkedExpr ) {
217
274
exists ( RefType t |
218
- t instanceof TypePath
275
+ t instanceof TypePath and checkedExpr = ma . getQualifier ( ) . getUnderlyingExpr ( )
219
276
or
220
- t .hasQualifiedName ( "kotlin.io" , "FilesKt" )
277
+ t instanceof FilesKt and
278
+ checkedExpr = ma .getArgument ( 0 ) .getUnderlyingExpr ( )
221
279
|
222
280
t = ma .getMethod ( ) .getDeclaringType ( ) and
223
- ma .getMethod ( ) .hasName ( "startsWith" )
281
+ ma .getMethod ( ) .hasName ( "startsWith" + [ "" , "$default" ] )
224
282
)
225
283
}
226
284
227
- private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
285
+ private predicate isDisallowedWord ( ConstantOrRegex word ) {
228
286
word .getStringValue ( ) .matches ( [ "/" , "\\" , "%WEB-INF%" , "%/data%" ] )
229
287
}
230
288
231
289
/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
232
290
private class PathTraversalGuard extends PathGuard {
291
+ Expr checkedExpr ;
292
+
233
293
PathTraversalGuard ( ) {
234
294
exists ( MethodAccess ma |
235
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
295
+ (
296
+ ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
297
+ checkedExpr = ma .getQualifier ( ) .getUnderlyingExpr ( )
298
+ or
299
+ ma .getMethod ( ) .getDeclaringType ( ) instanceof StringsKt and
300
+ checkedExpr = ma .getArgument ( 0 ) .getUnderlyingExpr ( )
301
+ ) and
236
302
ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
237
303
|
238
304
this = ma and
239
- ma .getMethod ( ) .hasName ( "contains" )
305
+ ma .getMethod ( ) .hasName ( "contains" + [ "" , "$default" ] )
240
306
or
241
307
exists ( EqualityTest eq |
242
308
this = eq and
243
- ma .getMethod ( ) .hasName ( [ "indexOf" , "lastIndexOf" ] ) and
309
+ ma .getMethod ( ) .hasName ( [ "indexOf" , "lastIndexOf" ] + [ "" , "$default" ] ) and
244
310
eq .getAnOperand ( ) = ma and
245
311
eq .getAnOperand ( ) .( CompileTimeConstantExpr ) .getIntValue ( ) = - 1
246
312
)
247
313
)
248
314
}
249
315
250
- override Expr getCheckedExpr ( ) {
251
- exists ( MethodAccess ma | ma = this .( EqualityTest ) .getAnOperand ( ) or ma = this |
252
- result = ma .getQualifier ( )
253
- )
254
- }
316
+ override Expr getCheckedExpr ( ) { result = checkedExpr }
255
317
256
318
boolean getBranch ( ) {
257
319
this instanceof MethodAccess and result = false
@@ -265,7 +327,7 @@ private class PathNormalizeSanitizer extends MethodAccess {
265
327
PathNormalizeSanitizer ( ) {
266
328
exists ( RefType t |
267
329
t instanceof TypePath or
268
- t . hasQualifiedName ( "kotlin.io" , " FilesKt" )
330
+ t instanceof FilesKt
269
331
|
270
332
this .getMethod ( ) .getDeclaringType ( ) = t and
271
333
this .getMethod ( ) .hasName ( "normalize" )
0 commit comments