1+ /** Provides classes and predicates to reason about sanitization of path injection vulnerabilities. */
2+
13import java
24private import semmle.code.java.controlflow.Guards
35private import semmle.code.java.dataflow.FlowSources
46private import semmle.code.java.dataflow.ExternalFlow
57
6- /**
7- * DEPRECATED: Use `PathTraversalSanitizer` instead.
8- *
9- * A barrier guard that protects against path traversal vulnerabilities.
10- */
11- abstract deprecated class PathTraversalBarrierGuard extends DataFlow:: BarrierGuard { }
12-
13- /** A sanitizer that protects against path traversal vulnerabilities. */
14- abstract class PathTraversalSanitizer extends DataFlow:: Node { }
8+ /** A sanitizer that protects against path injection vulnerabilities. */
9+ abstract class PathInjectionSanitizer extends DataFlow:: Node { }
1510
1611/**
1712 * Holds if `g` is guard that compares a string to a trusted value.
@@ -26,7 +21,7 @@ private predicate exactStringPathMatchGuard(Guard g, Expr e, boolean branch) {
2621 )
2722}
2823
29- private class ExactStringPathMatchSanitizer extends PathTraversalSanitizer {
24+ private class ExactStringPathMatchSanitizer extends PathInjectionSanitizer {
3025 ExactStringPathMatchSanitizer ( ) {
3126 this = DataFlow:: BarrierGuard< exactStringPathMatchGuard / 3 > :: getABarrierNode ( )
3227 }
@@ -38,7 +33,7 @@ private class ExactStringPathMatchSanitizer extends PathTraversalSanitizer {
3833 * This is used to look through field accessors such as `uri.getPath()`.
3934 */
4035private Expr getUnderlyingVarAccess ( Expr e ) {
41- result = getUnderlyingVarAccess ( e .( MethodAccess ) .getQualifier ( ) )
36+ result = getUnderlyingVarAccess ( e .( MethodAccess ) .getQualifier ( ) . getUnderlyingExpr ( ) )
4237 or
4338 result = e .( VarAccess )
4439}
@@ -49,7 +44,9 @@ private class AllowListGuard extends Guard instanceof MethodAccess {
4944 not isDisallowedWord ( super .getAnArgument ( ) )
5045 }
5146
52- Expr getCheckedExpr ( ) { result = getUnderlyingVarAccess ( super .getQualifier ( ) ) }
47+ Expr getCheckedExpr ( ) {
48+ result = getUnderlyingVarAccess ( super .getQualifier ( ) .getUnderlyingExpr ( ) )
49+ }
5350}
5451
5552/**
@@ -72,7 +69,7 @@ private predicate allowListGuard(Guard g, Expr e, boolean branch) {
7269 )
7370}
7471
75- private class AllowListSanitizer extends PathTraversalSanitizer {
72+ private class AllowListSanitizer extends PathInjectionSanitizer {
7673 AllowListSanitizer ( ) { this = DataFlow:: BarrierGuard< allowListGuard / 3 > :: getABarrierNode ( ) }
7774}
7875
@@ -90,7 +87,7 @@ private predicate dotDotCheckGuard(Guard g, Expr e, boolean branch) {
9087 )
9188}
9289
93- private class DotDotCheckSanitizer extends PathTraversalSanitizer {
90+ private class DotDotCheckSanitizer extends PathInjectionSanitizer {
9491 DotDotCheckSanitizer ( ) { this = DataFlow:: BarrierGuard< dotDotCheckGuard / 3 > :: getABarrierNode ( ) }
9592}
9693
@@ -100,7 +97,9 @@ private class BlockListGuard extends Guard instanceof MethodAccess {
10097 isDisallowedWord ( super .getAnArgument ( ) )
10198 }
10299
103- Expr getCheckedExpr ( ) { result = getUnderlyingVarAccess ( super .getQualifier ( ) ) }
100+ Expr getCheckedExpr ( ) {
101+ result = getUnderlyingVarAccess ( super .getQualifier ( ) .getUnderlyingExpr ( ) )
102+ }
104103}
105104
106105/**
@@ -123,7 +122,7 @@ private predicate blockListGuard(Guard g, Expr e, boolean branch) {
123122 )
124123}
125124
126- private class BlockListSanitizer extends PathTraversalSanitizer {
125+ private class BlockListSanitizer extends PathInjectionSanitizer {
127126 BlockListSanitizer ( ) { this = DataFlow:: BarrierGuard< blockListGuard / 3 > :: getABarrierNode ( ) }
128127}
129128
@@ -140,7 +139,7 @@ private predicate urlEncodingGuard(Guard g, Expr e, boolean branch) {
140139 )
141140}
142141
143- private class UrlEncodingSanitizer extends PathTraversalSanitizer {
142+ private class UrlEncodingSanitizer extends PathInjectionSanitizer {
144143 UrlEncodingSanitizer ( ) { this = DataFlow:: BarrierGuard< urlEncodingGuard / 3 > :: getABarrierNode ( ) }
145144}
146145
@@ -149,38 +148,51 @@ private class UrlEncodingSanitizer extends PathTraversalSanitizer {
149148 */
150149private predicate isStringPartialMatch ( MethodAccess ma ) {
151150 ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
152- ma .getMethod ( ) . getName ( ) =
153- [ "contains" , "startsWith" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ]
151+ ma .getMethod ( )
152+ . hasName ( [ "contains" , "startsWith" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ] )
154153}
155154
156155/**
157- * Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a partial path match.
156+ * Holds if `ma` is a call to a method that checks a partial path match.
158157 */
159158private predicate isPathPartialMatch ( MethodAccess ma ) {
160159 ma .getMethod ( ) .getDeclaringType ( ) instanceof TypePath and
161- ma .getMethod ( ) .getName ( ) = "startsWith"
160+ ma .getMethod ( ) .hasName ( "startsWith" )
161+ or
162+ ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "kotlin.io" , "FilesKt" ) and
163+ ma .getMethod ( ) .hasName ( "startsWith" )
162164}
163165
164166private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
165167 word .getStringValue ( ) .matches ( [ "%WEB-INF%" , "%META-INF%" , "%..%" ] )
166168}
167169
168170/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
169- class PathTraversalGuard extends Guard instanceof MethodAccess {
171+ private class PathTraversalGuard extends Guard instanceof MethodAccess {
170172 PathTraversalGuard ( ) {
171173 super .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
172174 super .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
173175 super .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
174176 }
175177
176- Expr getCheckedExpr ( ) { result = getUnderlyingVarAccess ( super .getQualifier ( ) ) }
178+ Expr getCheckedExpr ( ) {
179+ result = getUnderlyingVarAccess ( super .getQualifier ( ) .getUnderlyingExpr ( ) )
180+ }
177181}
178182
179183/** A complementary sanitizer that protects against path traversal using path normalization. */
180184private class PathNormalizeSanitizer extends MethodAccess {
181185 PathNormalizeSanitizer ( ) {
182- this .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.nio.file" , "Path" ) and
183- this .getMethod ( ) .hasName ( "normalize" )
186+ exists ( RefType t |
187+ t instanceof TypePath or
188+ t .hasQualifiedName ( "kotlin.io" , "FilesKt" )
189+ |
190+ this .getMethod ( ) .getDeclaringType ( ) = t and
191+ this .getMethod ( ) .hasName ( "normalize" )
192+ )
193+ or
194+ this .getMethod ( ) .getDeclaringType ( ) instanceof TypeFile and
195+ this .getMethod ( ) .hasName ( [ "getCanonicalPath" , "getCanonicalFile" ] )
184196 }
185197}
186198
@@ -198,8 +210,13 @@ private class UrlEncodingGuard extends Guard instanceof MethodAccess {
198210/** A complementary sanitizer that protects against double URL encoding using URL decoding. */
199211private class UrlDecodeSanitizer extends MethodAccess {
200212 UrlDecodeSanitizer ( ) {
201- this .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.net" , "URLDecoder" ) and
202- this .getMethod ( ) .hasName ( "decode" )
213+ exists ( RefType t |
214+ this .getMethod ( ) .getDeclaringType ( ) = t and
215+ this .getMethod ( ) .hasName ( "decode" )
216+ |
217+ t .hasQualifiedName ( "java.net" , "URLDecoder" ) or
218+ t .hasQualifiedName ( "android.net" , "Uri" )
219+ )
203220 }
204221}
205222
@@ -209,14 +226,3 @@ class NormalizedPathNode extends DataFlow::Node {
209226 TaintTracking:: localExprTaint ( this .asExpr ( ) , any ( PathNormalizeSanitizer ma ) )
210227 }
211228}
212-
213- /** Data model related to `java.nio.file.Path`. */
214- private class PathDataModel extends SummaryModelCsv {
215- override predicate row ( string row ) {
216- row =
217- [
218- "java.nio.file;Paths;true;get;;;Argument[0];ReturnValue;taint;manual" ,
219- "java.nio.file;Path;true;normalize;;;Argument[-1];ReturnValue;taint;manual"
220- ]
221- }
222- }
0 commit comments