@@ -34,14 +34,6 @@ private class FollowsSanitizingPrefix extends UnsafeUrlForwardSanitizer {
34
34
/** A barrier guard that protects against URL forward vulnerabilities. */
35
35
abstract class UnsafeUrlForwardBarrierGuard extends DataFlow:: BarrierGuard { }
36
36
37
- /**
38
- * Holds if `ma` is a call to a method that checks exact match of string.
39
- */
40
- private predicate isExactStringPathMatch ( MethodAccess ma ) {
41
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
42
- ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ]
43
- }
44
-
45
37
/**
46
38
* Holds if `ma` is a call to a method that checks a path string.
47
39
*/
@@ -59,105 +51,127 @@ private predicate isFilePathMatch(MethodAccess ma) {
59
51
ma .getMethod ( ) .getName ( ) = "startsWith"
60
52
}
61
53
62
- /**
63
- * Holds if `ma` protects against path traversal, by either:
64
- * * looking for the literal `..`
65
- * * performing path normalization
66
- */
67
- private predicate isPathTraversalCheck ( MethodAccess ma , Expr checked ) {
68
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
69
- ma .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
70
- ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".." and
71
- ma .( Guard ) .controls ( checked .getBasicBlock ( ) , false )
72
- or
73
- ma .getMethod ( ) instanceof PathNormalizeMethod and
74
- checked = ma
54
+ /** A complementary guard that protects against path traversal, by looking for the literal `..`. */
55
+ private class PathTraversalGuard extends Guard instanceof MethodAccess {
56
+ Expr checked ;
57
+
58
+ PathTraversalGuard ( ) {
59
+ this .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
60
+ this .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
61
+ this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".." and
62
+ this .controls ( checked .getBasicBlock ( ) , false )
63
+ }
64
+
65
+ predicate checks ( Expr e ) { checked = e }
75
66
}
76
67
77
- /**
78
- * Holds if `ma` protects against double URL encoding, by either:
79
- * * looking for the literal `%`
80
- * * performing URL decoding
81
- */
82
- private predicate isURLEncodingCheck ( MethodAccess ma , Expr checked ) {
83
- // Search the special character `%` used in url encoding
84
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
85
- ma .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
86
- ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%" and
87
- ma .( Guard ) .controls ( checked .getBasicBlock ( ) , false )
88
- or
89
- // Call to `URLDecoder` assuming the implementation handles double encoding correctly
90
- ma .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.net" , "URLDecoder" ) and
91
- ma .getMethod ( ) .hasName ( "decode" ) and
92
- checked = ma
68
+ /** A complementary sanitizer that protects against path traversal using path normalization. */
69
+ private class PathNormalizeSanitizer extends MethodAccess {
70
+ PathNormalizeSanitizer ( ) {
71
+ this .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.nio.file" , "Path" ) and
72
+ this .getMethod ( ) .hasName ( "normalize" )
73
+ }
74
+ }
75
+
76
+ /** A complementary guard that protects against double URL encoding, by looking for the literal `%`. */
77
+ private class UrlEncodingGuard extends Guard instanceof MethodAccess {
78
+ Expr checked ;
79
+
80
+ UrlEncodingGuard ( ) {
81
+ this .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
82
+ this .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
83
+ this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%" and
84
+ this .controls ( checked .getBasicBlock ( ) , false )
85
+ }
86
+
87
+ predicate checks ( Expr e ) { checked = e }
93
88
}
94
89
95
- /** The Java method `normalize` of `java.nio.file.Path` . */
96
- private class PathNormalizeMethod extends Method {
97
- PathNormalizeMethod ( ) {
98
- this .getDeclaringType ( ) .hasQualifiedName ( "java.nio.file " , "Path " ) and
99
- this .hasName ( "normalize " )
90
+ /** A complementary sanitizer that protects against double URL encoding using URL decoding . */
91
+ private class UrlDecodeSanitizer extends MethodAccess {
92
+ UrlDecodeSanitizer ( ) {
93
+ this .getMethod ( ) . getDeclaringType ( ) .hasQualifiedName ( "java.net " , "URLDecoder " ) and
94
+ this .getMethod ( ) . hasName ( "decode " )
100
95
}
101
96
}
102
97
103
98
private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
104
99
word .getStringValue ( ) .matches ( [ "%WEB-INF%" , "%META-INF%" , "%..%" ] )
105
100
}
106
101
107
- private predicate isAllowListCheck ( MethodAccess ma ) {
108
- ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
109
- not isDisallowedWord ( ma .getAnArgument ( ) )
110
- }
102
+ /**
103
+ * A guard that considers safe a string being exactly compared to a trusted value.
104
+ */
105
+ private class ExactStringPathMatchGuard extends UnsafeUrlForwardBarrierGuard instanceof MethodAccess {
106
+ ExactStringPathMatchGuard ( ) {
107
+ this .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
108
+ this .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ]
109
+ }
111
110
112
- private predicate isDisallowListCheck ( MethodAccess ma ) {
113
- ( isStringPathMatch ( ma ) or isFilePathMatch ( ma ) ) and
114
- isDisallowedWord ( ma .getAnArgument ( ) )
111
+ override predicate checks ( Expr e , boolean branch ) {
112
+ e = this .( MethodAccess ) .getQualifier ( ) and
113
+ branch = true
114
+ }
115
115
}
116
116
117
117
/**
118
- * A guard that checks a path with the following methods:
119
- * 1. Exact string match
120
- * 2. Path matches allowed values (needs to protect against path traversal)
121
- * 3. Path matches disallowed values (needs to protect against URL encoding)
118
+ * A guard that considers safe a string being matched against an allowlist of partial trusted values.
119
+ * This requires additional protection against path traversal, either another guard (`PathTraversalGuard`)
120
+ * or a sanitizer (`PathNormalizeSanitizer`).
122
121
*/
123
- private class PathMatchGuard extends UnsafeUrlForwardBarrierGuard {
124
- PathMatchGuard ( ) {
125
- isExactStringPathMatch ( this ) or isAllowListCheck ( this ) or isDisallowListCheck ( this )
122
+ private class AllowListCheckGuard extends UnsafeUrlForwardBarrierGuard instanceof MethodAccess {
123
+ AllowListCheckGuard ( ) {
124
+ ( isStringPathMatch ( this ) or isFilePathMatch ( this ) ) and
125
+ not isDisallowedWord ( this .getAnArgument ( ) )
126
126
}
127
127
128
128
override predicate checks ( Expr e , boolean branch ) {
129
129
e = this .( MethodAccess ) .getQualifier ( ) and
130
+ branch = true and
130
131
(
131
- isExactStringPathMatch ( this ) and
132
- branch = true
132
+ // Either the path normalization sanitizer comes before the guard
133
+ exists ( PathNormalizeSanitizer sanitizer | DataFlow :: localExprFlow ( sanitizer , e ) )
133
134
or
134
- // When using an allowlist, that is, checking for known safe paths
135
- // (for example, `path.startsWith(BASE_PATH)`)
136
- // the application needs to protect against path traversal bypasses.
137
- isAllowListCheck ( this ) and
138
- exists ( MethodAccess ma , Expr checked | isPathTraversalCheck ( ma , checked ) |
139
- // Either the path traversal check comes before the guard
140
- DataFlow:: localExprFlow ( checked , e ) or
135
+ // or the path traversal check comes before the guard
136
+ exists ( PathTraversalGuard guard |
137
+ guard .checks ( any ( Expr checked | DataFlow:: localExprFlow ( checked , e ) ) ) or
141
138
// or both checks are in the same condition
142
139
// (for example, `path.startsWith(BASE_PATH) && !path.contains("..")`)
143
- ma .( Guard ) .controls ( this .getBasicBlock ( ) , _) or
144
- this .controls ( ma .getBasicBlock ( ) , branch )
145
- ) and
146
- branch = true
140
+ guard .controls ( this .getBasicBlock ( ) .( ConditionBlock ) , false ) or
141
+ this .controls ( guard .getBasicBlock ( ) .( ConditionBlock ) , branch )
142
+ )
143
+ )
144
+ }
145
+ }
146
+
147
+ /**
148
+ * A guard that considers safe a string being matched against a blocklist of known dangerous values.
149
+ * This requires additional protection against path traversal, either another guard (`UrlEncodingGuard`)
150
+ * or a sanitizer (`UrlDecodeSanitizer`).
151
+ */
152
+ private class BlockListCheckGuard extends UnsafeUrlForwardBarrierGuard instanceof MethodAccess {
153
+ BlockListCheckGuard ( ) {
154
+ ( isStringPathMatch ( this ) or isFilePathMatch ( this ) ) and
155
+ isDisallowedWord ( this .getAnArgument ( ) )
156
+ }
157
+
158
+ override predicate checks ( Expr e , boolean branch ) {
159
+ e = this .( MethodAccess ) .getQualifier ( ) and
160
+ branch = false and
161
+ (
162
+ // Either the URL decode sanitization comes before the guard
163
+ exists ( UrlDecodeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
147
164
or
148
- // When using a blocklist, that is, checking for known bad patterns in the path,
149
- // (for example, `path.startsWith("/WEB-INF/")` or `path.contains("..")`)
150
- // the application needs to protect against double URL encoding bypasses.
151
- isDisallowListCheck ( this ) and
152
- exists ( MethodAccess ma , Expr checked | isURLEncodingCheck ( ma , checked ) |
153
- // Either the URL encoding check comes before the guard
154
- DataFlow:: localExprFlow ( checked , e ) or
165
+ // or the URL encoding check comes before the guard
166
+ exists ( UrlEncodingGuard guard |
167
+ guard .checks ( any ( Expr checked | DataFlow:: localExprFlow ( checked , e ) ) )
168
+ or
155
169
// or both checks are in the same condition
156
170
// (for example, `!path.contains("..") && !path.contains("%")`)
157
- ma . ( Guard ) . controls ( this .getBasicBlock ( ) , _ ) or
158
- this . controls ( ma . getBasicBlock ( ) , branch )
159
- ) and
160
- branch = false
171
+ guard . controls ( this .getBasicBlock ( ) . ( ConditionBlock ) , false )
172
+ or
173
+ this . controls ( guard . getBasicBlock ( ) . ( ConditionBlock ) , branch )
174
+ )
161
175
)
162
176
}
163
177
}
0 commit comments