@@ -5,6 +5,8 @@ private import semmle.code.java.controlflow.Guards
55private import semmle.code.java.dataflow.ExternalFlow
66private import semmle.code.java.dataflow.FlowSources
77private import semmle.code.java.dataflow.SSA
8+ private import semmle.code.java.frameworks.kotlin.IO
9+ private import semmle.code.java.frameworks.kotlin.Text
810
911/** A sanitizer that protects against path injection vulnerabilities. */
1012abstract class PathInjectionSanitizer extends DataFlow:: Node { }
@@ -47,16 +49,25 @@ private module ValidationMethod<DataFlow::guardChecksSig/3 validationGuard> {
4749 */
4850private predicate exactPathMatchGuard ( Guard g , Expr e , boolean branch ) {
4951 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 ( )
5567 |
5668 ma .getMethod ( ) .getDeclaringType ( ) = t and
5769 ma = g and
58- ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ] and
59- e = ma .getQualifier ( ) and
70+ ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ] + [ "" , "$default" ] and
6071 branch = true
6172 )
6273}
@@ -82,12 +93,18 @@ private predicate localTaintFlowToPathGuard(Expr e, PathGuard g) {
8293}
8394
8495private class AllowedPrefixGuard extends PathGuard instanceof MethodAccess {
96+ Expr checkedExpr ;
97+
8598 AllowedPrefixGuard ( ) {
86- ( isStringPrefixMatch ( this ) or isPathPrefixMatch ( this ) ) and
99+ (
100+ isStringPrefixMatch ( this , checkedExpr )
101+ or
102+ isPathPrefixMatch ( this , checkedExpr )
103+ ) and
87104 not isDisallowedWord ( super .getAnArgument ( ) )
88105 }
89106
90- override Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
107+ override Expr getCheckedExpr ( ) { result = checkedExpr }
91108}
92109
93110/**
@@ -149,12 +166,18 @@ private class DotDotCheckSanitizer extends PathInjectionSanitizer {
149166}
150167
151168private class BlockListGuard extends PathGuard instanceof MethodAccess {
169+ Expr checkedExpr ;
170+
152171 BlockListGuard ( ) {
153- ( isStringPartialMatch ( this ) or isPathPrefixMatch ( this ) ) and
172+ (
173+ isStringPartialMatch ( this , checkedExpr )
174+ or
175+ isPathPrefixMatch ( this , checkedExpr )
176+ ) and
154177 isDisallowedWord ( super .getAnArgument ( ) )
155178 }
156179
157- override Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
180+ override Expr getCheckedExpr ( ) { result = checkedExpr }
158181}
159182
160183/**
@@ -188,70 +211,109 @@ private class BlockListSanitizer extends PathInjectionSanitizer {
188211 }
189212}
190213
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" ] )
197238 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+ )
200250 )
201251}
202252
203253/**
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` .
205255 */
206- private predicate isStringPartialMatch ( MethodAccess ma ) {
207- isStringPrefixMatch ( ma )
256+ private predicate isStringPartialMatch ( MethodAccess ma , Expr checkedExpr ) {
257+ isStringPrefixMatch ( ma , checkedExpr )
208258 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" ] )
211268}
212269
213270/**
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.
215272 */
216- private predicate isPathPrefixMatch ( MethodAccess ma ) {
273+ private predicate isPathPrefixMatch ( MethodAccess ma , Expr checkedExpr ) {
217274 exists ( RefType t |
218- t instanceof TypePath
275+ t instanceof TypePath and checkedExpr = ma . getQualifier ( ) . getUnderlyingExpr ( )
219276 or
220- t .hasQualifiedName ( "kotlin.io" , "FilesKt" )
277+ t instanceof FilesKt and
278+ checkedExpr = ma .getArgument ( 0 ) .getUnderlyingExpr ( )
221279 |
222280 t = ma .getMethod ( ) .getDeclaringType ( ) and
223- ma .getMethod ( ) .hasName ( "startsWith" )
281+ ma .getMethod ( ) .hasName ( "startsWith" + [ "" , "$default" ] )
224282 )
225283}
226284
227- private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
285+ private predicate isDisallowedWord ( ConstantOrRegex word ) {
228286 word .getStringValue ( ) .matches ( [ "/" , "\\" , "%WEB-INF%" , "%/data%" ] )
229287}
230288
231289/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
232290private class PathTraversalGuard extends PathGuard {
291+ Expr checkedExpr ;
292+
233293 PathTraversalGuard ( ) {
234294 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
236302 ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
237303 |
238304 this = ma and
239- ma .getMethod ( ) .hasName ( "contains" )
305+ ma .getMethod ( ) .hasName ( "contains" + [ "" , "$default" ] )
240306 or
241307 exists ( EqualityTest eq |
242308 this = eq and
243- ma .getMethod ( ) .hasName ( [ "indexOf" , "lastIndexOf" ] ) and
309+ ma .getMethod ( ) .hasName ( [ "indexOf" , "lastIndexOf" ] + [ "" , "$default" ] ) and
244310 eq .getAnOperand ( ) = ma and
245311 eq .getAnOperand ( ) .( CompileTimeConstantExpr ) .getIntValue ( ) = - 1
246312 )
247313 )
248314 }
249315
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 }
255317
256318 boolean getBranch ( ) {
257319 this instanceof MethodAccess and result = false
@@ -265,7 +327,7 @@ private class PathNormalizeSanitizer extends MethodAccess {
265327 PathNormalizeSanitizer ( ) {
266328 exists ( RefType t |
267329 t instanceof TypePath or
268- t . hasQualifiedName ( "kotlin.io" , " FilesKt" )
330+ t instanceof FilesKt
269331 |
270332 this .getMethod ( ) .getDeclaringType ( ) = t and
271333 this .getMethod ( ) .hasName ( "normalize" )
0 commit comments