11import java
2- import DataFlow
3- import semmle.code.java.controlflow.Guards
42import semmle.code.java.dataflow.FlowSources
5- import semmle.code.java.frameworks.Servlets
6- import semmle.code.java.frameworks.spring.SpringWeb
7- import semmle.code.java.security.RequestForgery
83private import semmle.code.java.dataflow.StringPrefixes
94
105/** A sink for unsafe URL forward vulnerabilities. */
@@ -13,9 +8,6 @@ abstract class UnsafeUrlForwardSink extends DataFlow::Node { }
138/** A sanitizer for unsafe URL forward vulnerabilities. */
149abstract class UnsafeUrlForwardSanitizer extends DataFlow:: Node { }
1510
16- /** A barrier guard that protects against URL forward vulnerabilities. */
17- abstract class UnsafeUrlForwardBarrierGuard extends DataFlow:: BarrierGuard { }
18-
1911/** An argument to `getRequestDispatcher`. */
2012private class RequestDispatcherSink extends UnsafeUrlForwardSink {
2113 RequestDispatcherSink ( ) {
@@ -59,175 +51,6 @@ private class FollowsSanitizingPrefix extends UnsafeUrlForwardSanitizer {
5951 FollowsSanitizingPrefix ( ) { this .asExpr ( ) = any ( SanitizingPrefix fp ) .getAnAppendedExpression ( ) }
6052}
6153
62- /**
63- * A guard that considers safe a string being exactly compared to a trusted value.
64- */
65- private class ExactStringPathMatchGuard extends UnsafeUrlForwardBarrierGuard instanceof MethodAccess {
66- ExactStringPathMatchGuard ( ) {
67- super .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
68- super .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ]
69- }
70-
71- override predicate checks ( Expr e , boolean branch ) {
72- e = super .getQualifier ( ) and
73- branch = true
74- }
75- }
76-
77- private class AllowListGuard extends Guard instanceof MethodAccess {
78- AllowListGuard ( ) {
79- ( isStringPartialMatch ( this ) or isPathPartialMatch ( this ) ) and
80- not isDisallowedWord ( super .getAnArgument ( ) )
81- }
82-
83- Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
84- }
85-
86- /**
87- * A guard that considers a path safe because it is checked against an allowlist of partial trusted values.
88- * This requires additional protection against path traversal, either another guard (`PathTraversalGuard`)
89- * or a sanitizer (`PathNormalizeSanitizer`), to ensure any internal `..` components are removed from the path.
90- */
91- private class AllowListBarrierGuard extends UnsafeUrlForwardBarrierGuard instanceof AllowListGuard {
92- override predicate checks ( Expr e , boolean branch ) {
93- e = super .getCheckedExpr ( ) and
94- branch = true and
95- (
96- // Either a path normalization sanitizer comes before the guard,
97- exists ( PathNormalizeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
98- or
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 )
103- )
104- )
105- }
106- }
107-
108- /**
109- * A guard that considers a path safe because it is checked for `..` components, having previously
110- * been checked for a trusted prefix.
111- */
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 ( ) {
126- ( isStringPartialMatch ( this ) or isPathPartialMatch ( this ) ) and
127- isDisallowedWord ( super .getAnArgument ( ) )
128- }
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 {
139- override predicate checks ( Expr e , boolean branch ) {
140- e = super .getCheckedExpr ( ) and
141- branch = false and
142- (
143- // Either `e` has been URL decoded:
144- exists ( UrlDecodeSanitizer sanitizer | DataFlow:: localExprFlow ( sanitizer , e ) )
145- or
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 )
150- )
151- )
152- }
153- }
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-
170- /**
171- * Holds if `ma` is a call to a method that checks a partial string match.
172- */
173- private predicate isStringPartialMatch ( MethodAccess ma ) {
174- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
175- ma .getMethod ( ) .getName ( ) =
176- [ "contains" , "startsWith" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ]
177- }
178-
179- /**
180- * Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a partial path match.
181- */
182- private predicate isPathPartialMatch ( MethodAccess ma ) {
183- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypePath and
184- ma .getMethod ( ) .getName ( ) = "startsWith"
185- }
186-
187- private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
188- word .getStringValue ( ) .matches ( [ "%WEB-INF%" , "%META-INF%" , "%..%" ] )
189- }
190-
191- /** A complementary guard that protects against path traversal, by looking for the literal `..`. */
192- private class PathTraversalGuard extends Guard instanceof MethodAccess {
193- Expr checked ;
194-
195- PathTraversalGuard ( ) {
196- super .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
197- super .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
198- super .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
199- }
200-
201- Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
202- }
203-
204- /** A complementary sanitizer that protects against path traversal using path normalization. */
205- private class PathNormalizeSanitizer extends MethodAccess {
206- PathNormalizeSanitizer ( ) {
207- this .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.nio.file" , "Path" ) and
208- this .getMethod ( ) .hasName ( "normalize" )
209- }
210- }
211-
212- /** A complementary guard that protects against double URL encoding, by looking for the literal `%`. */
213- private class UrlEncodingGuard extends Guard instanceof MethodAccess {
214- UrlEncodingGuard ( ) {
215- super .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
216- super .getMethod ( ) .hasName ( [ "contains" , "indexOf" ] ) and
217- super .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = "%"
218- }
219-
220- Expr getCheckedExpr ( ) { result = super .getQualifier ( ) }
221- }
222-
223- /** A complementary sanitizer that protects against double URL encoding using URL decoding. */
224- private class UrlDecodeSanitizer extends MethodAccess {
225- UrlDecodeSanitizer ( ) {
226- this .getMethod ( ) .getDeclaringType ( ) .hasQualifiedName ( "java.net" , "URLDecoder" ) and
227- this .getMethod ( ) .hasName ( "decode" )
228- }
229- }
230-
23154private class ForwardPrefix extends InterestingPrefix {
23255 ForwardPrefix ( ) { this .getStringValue ( ) = "forward:" }
23356
0 commit comments