@@ -74,68 +74,99 @@ private class ExactStringPathMatchGuard extends UnsafeUrlForwardBarrierGuard ins
74
74
}
75
75
}
76
76
77
+ private class AllowListGuard extends Guard instanceof MethodAccess {
78
+ AllowListGuard ( ) {
79
+ ( isStringPartialMatch ( this .( MethodAccess ) ) or isPathPartialMatch ( this .( MethodAccess ) ) ) and
80
+ not isDisallowedWord ( this .( MethodAccess ) .getAnArgument ( ) )
81
+ }
82
+
83
+ Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
84
+ }
85
+
77
86
/**
78
- * A guard that considers safe a string being matched against an allowlist of partial trusted values.
87
+ * A guard that considers a path safe because it is checked against an allowlist of partial trusted values.
79
88
* This requires additional protection against path traversal, either another guard (`PathTraversalGuard`)
80
- * or a sanitizer (`PathNormalizeSanitizer`).
89
+ * or a sanitizer (`PathNormalizeSanitizer`), to ensure any internal `..` components are removed from the path .
81
90
*/
82
- private class AllowListCheckGuard extends UnsafeUrlForwardBarrierGuard instanceof MethodAccess {
83
- AllowListCheckGuard ( ) {
84
- ( isStringPartialMatch ( this ) or isPathPartialMatch ( this ) ) and
85
- not isDisallowedWord ( this .getAnArgument ( ) )
86
- }
87
-
91
+ private class AllowListBarrierGuard extends UnsafeUrlForwardBarrierGuard instanceof AllowListGuard {
88
92
override predicate checks ( Expr e , boolean branch ) {
89
- e = this . ( MethodAccess ) . getQualifier ( ) and
93
+ e = super . getCheckedExpr ( ) and
90
94
branch = true and
91
95
(
92
- // Either the path normalization sanitizer comes before the guard
96
+ // Either a path normalization sanitizer comes before the guard,
93
97
exists ( PathNormalizeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
94
98
or
95
- // or the path traversal check comes before the guard
96
- exists ( PathTraversalGuard guard |
97
- guard .checks ( any ( Expr checked | DataFlow:: localExprFlow ( checked , e ) ) ) or
98
- // or both checks are in the same condition
99
- // (for example, `path.startsWith(BASE_PATH) && !path.contains("..")`)
100
- guard .controls ( this .getBasicBlock ( ) .( ConditionBlock ) , false ) or
101
- this .controls ( guard .getBasicBlock ( ) .( ConditionBlock ) , branch )
99
+ // or a check like `!path.contains("..")` comes before the guard
100
+ exists ( PathTraversalGuard previousGuard |
101
+ DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
102
+ previousGuard .controls ( this .getBasicBlock ( ) .( ConditionBlock ) , false )
102
103
)
103
104
)
104
105
}
105
106
}
106
107
107
108
/**
108
- * A guard that considers safe a string being matched against a blocklist of known dangerous values.
109
- * This requires additional protection against path traversal, either another guard (`UrlEncodingGuard`)
110
- * or a sanitizer (`UrlDecodeSanitizer`).
109
+ * A guard that considers a path safe because it is checked for `..` components, having previously
110
+ * been checked for a trusted prefix.
111
111
*/
112
- private class BlockListCheckGuard extends UnsafeUrlForwardBarrierGuard instanceof MethodAccess {
113
- BlockListCheckGuard ( ) {
112
+ private class DotDotCheckBarrierGuard extends UnsafeUrlForwardBarrierGuard instanceof PathTraversalGuard {
113
+ override predicate checks ( Expr e , boolean branch ) {
114
+ e = super .getCheckedExpr ( ) and
115
+ branch = false and
116
+ // The same value has previously been checked against a list of allowed prefixes:
117
+ exists ( AllowListGuard previousGuard |
118
+ DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
119
+ previousGuard .controls ( this .getBasicBlock ( ) .( ConditionBlock ) , true )
120
+ )
121
+ }
122
+ }
123
+
124
+ private class BlockListGuard extends Guard instanceof MethodAccess {
125
+ BlockListGuard ( ) {
114
126
( isStringPartialMatch ( this ) or isPathPartialMatch ( this ) ) and
115
127
isDisallowedWord ( this .getAnArgument ( ) )
116
128
}
117
129
130
+ Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
131
+ }
132
+
133
+ /**
134
+ * A guard that considers a string safe because it is checked against a blocklist of known dangerous values.
135
+ * This requires a prior check for URL encoding concealing a forbidden value, either a guard (`UrlEncodingGuard`)
136
+ * or a sanitizer (`UrlDecodeSanitizer`).
137
+ */
138
+ private class BlockListBarrierGuard extends UnsafeUrlForwardBarrierGuard instanceof BlockListGuard {
118
139
override predicate checks ( Expr e , boolean branch ) {
119
- e = this . ( MethodAccess ) . getQualifier ( ) and
140
+ e = super . getCheckedExpr ( ) and
120
141
branch = false and
121
142
(
122
- // Either the URL decode sanitization comes before the guard
143
+ // Either `e` has been URL decoded:
123
144
exists ( UrlDecodeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
124
145
or
125
- // or the URL encoding check comes before the guard
126
- exists ( UrlEncodingGuard guard |
127
- guard .checks ( any ( Expr checked | DataFlow:: localExprFlow ( checked , e ) ) )
128
- or
129
- // or both checks are in the same condition
130
- // (for example, `!path.contains("..") && !path.contains("%")`)
131
- guard .controls ( this .getBasicBlock ( ) .( ConditionBlock ) , false )
132
- or
133
- this .controls ( guard .getBasicBlock ( ) .( ConditionBlock ) , branch )
146
+ // or `e` has previously been checked for URL encoding sequences:
147
+ exists ( UrlEncodingGuard previousGuard |
148
+ DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
149
+ previousGuard .controls ( this .getBasicBlock ( ) , false )
134
150
)
135
151
)
136
152
}
137
153
}
138
154
155
+ /**
156
+ * A guard that considers a string safe because it is checked for URL encoding sequences,
157
+ * having previously been checked against a block-list of forbidden values.
158
+ */
159
+ private class URLEncodingBarrierGuard extends UnsafeUrlForwardBarrierGuard instanceof UrlEncodingGuard {
160
+ override predicate checks ( Expr e , boolean branch ) {
161
+ e = super .getCheckedExpr ( ) and
162
+ branch = false and
163
+ exists ( BlockListGuard previousGuard |
164
+ DataFlow:: localExprFlow ( previousGuard .getCheckedExpr ( ) , e ) and
165
+ previousGuard .controls ( this .getBasicBlock ( ) , false )
166
+ )
167
+ }
168
+ }
169
+
139
170
/**
140
171
* Holds if `ma` is a call to a method that checks a partial string match.
141
172
*/
@@ -164,11 +195,10 @@ private class PathTraversalGuard extends Guard instanceof MethodAccess {
164
195
PathTraversalGuard ( ) {
165
196
this .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
166
197
this .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
167
- this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".." and
168
- this .controls ( checked .getBasicBlock ( ) , false )
198
+ this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
169
199
}
170
200
171
- predicate checks ( Expr e ) { checked = e }
201
+ Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
172
202
}
173
203
174
204
/** A complementary sanitizer that protects against path traversal using path normalization. */
@@ -181,16 +211,13 @@ private class PathNormalizeSanitizer extends MethodAccess {
181
211
182
212
/** A complementary guard that protects against double URL encoding, by looking for the literal `%`. */
183
213
private class UrlEncodingGuard extends Guard instanceof MethodAccess {
184
- Expr checked ;
185
-
186
214
UrlEncodingGuard ( ) {
187
215
this .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
188
216
this .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
189
- this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%" and
190
- this .controls ( checked .getBasicBlock ( ) , false )
217
+ this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%"
191
218
}
192
219
193
- predicate checks ( Expr e ) { checked = e }
220
+ Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
194
221
}
195
222
196
223
/** A complementary sanitizer that protects against double URL encoding using URL decoding. */
0 commit comments