1
1
import java
2
- import DataFlow
3
- import semmle.code.java.controlflow.Guards
4
2
import 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
8
3
private import semmle.code.java.dataflow.StringPrefixes
9
4
10
5
/** A sink for unsafe URL forward vulnerabilities. */
@@ -13,9 +8,6 @@ abstract class UnsafeUrlForwardSink extends DataFlow::Node { }
13
8
/** A sanitizer for unsafe URL forward vulnerabilities. */
14
9
abstract class UnsafeUrlForwardSanitizer extends DataFlow:: Node { }
15
10
16
- /** A barrier guard that protects against URL forward vulnerabilities. */
17
- abstract class UnsafeUrlForwardBarrierGuard extends DataFlow:: BarrierGuard { }
18
-
19
11
/** An argument to `getRequestDispatcher`. */
20
12
private class RequestDispatcherSink extends UnsafeUrlForwardSink {
21
13
RequestDispatcherSink ( ) {
@@ -59,175 +51,6 @@ private class FollowsSanitizingPrefix extends UnsafeUrlForwardSanitizer {
59
51
FollowsSanitizingPrefix ( ) { this .asExpr ( ) = any ( SanitizingPrefix fp ) .getAnAppendedExpression ( ) }
60
52
}
61
53
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
-
231
54
private class ForwardPrefix extends InterestingPrefix {
232
55
ForwardPrefix ( ) { this .getStringValue ( ) = "forward:" }
233
56
0 commit comments