@@ -74,68 +74,99 @@ private class ExactStringPathMatchGuard extends UnsafeUrlForwardBarrierGuard ins
7474 }
7575}
7676
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+
7786/**
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.
7988 * 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 .
8190 */
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 {
8892 override predicate checks ( Expr e , boolean branch ) {
89- e = this . ( MethodAccess ) . getQualifier ( ) and
93+ e = super . getCheckedExpr ( ) and
9094 branch = true and
9195 (
92- // Either the path normalization sanitizer comes before the guard
96+ // Either a path normalization sanitizer comes before the guard,
9397 exists ( PathNormalizeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
9498 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 )
102103 )
103104 )
104105 }
105106}
106107
107108/**
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.
111111 */
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 ( ) {
114126 ( isStringPartialMatch ( this ) or isPathPartialMatch ( this ) ) and
115127 isDisallowedWord ( this .getAnArgument ( ) )
116128 }
117129
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 {
118139 override predicate checks ( Expr e , boolean branch ) {
119- e = this . ( MethodAccess ) . getQualifier ( ) and
140+ e = super . getCheckedExpr ( ) and
120141 branch = false and
121142 (
122- // Either the URL decode sanitization comes before the guard
143+ // Either `e` has been URL decoded:
123144 exists ( UrlDecodeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
124145 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 )
134150 )
135151 )
136152 }
137153}
138154
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+
139170/**
140171 * Holds if `ma` is a call to a method that checks a partial string match.
141172 */
@@ -164,11 +195,10 @@ private class PathTraversalGuard extends Guard instanceof MethodAccess {
164195 PathTraversalGuard ( ) {
165196 this .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
166197 this .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
167- this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".." and
168- this .controls ( checked .getBasicBlock ( ) , false )
198+ this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
169199 }
170200
171- predicate checks ( Expr e ) { checked = e }
201+ Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
172202}
173203
174204/** A complementary sanitizer that protects against path traversal using path normalization. */
@@ -181,16 +211,13 @@ private class PathNormalizeSanitizer extends MethodAccess {
181211
182212/** A complementary guard that protects against double URL encoding, by looking for the literal `%`. */
183213private class UrlEncodingGuard extends Guard instanceof MethodAccess {
184- Expr checked ;
185-
186214 UrlEncodingGuard ( ) {
187215 this .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
188216 this .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
189- this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%" and
190- this .controls ( checked .getBasicBlock ( ) , false )
217+ this .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%"
191218 }
192219
193- predicate checks ( Expr e ) { checked = e }
220+ Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
194221}
195222
196223/** A complementary sanitizer that protects against double URL encoding using URL decoding. */
0 commit comments