Skip to content

Commit f011bbc

Browse files
authored
Merge pull request #8055 from luchua-bc/java/unsafe-url-forward-with-shared-lib
CWE-552: Switch to the shared PathSanitizer library
2 parents 517d696 + f136ea0 commit f011bbc

File tree

2 files changed

+2
-179
lines changed

2 files changed

+2
-179
lines changed

java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import java
1414
import UnsafeUrlForward
15-
import semmle.code.java.dataflow.FlowSources
15+
import experimental.semmle.code.java.PathSanitizer
1616
import DataFlow::PathGraph
1717

1818
class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
@@ -35,7 +35,7 @@ class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
3535
override predicate isSanitizer(DataFlow::Node node) { node instanceof UnsafeUrlForwardSanitizer }
3636

3737
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
38-
guard instanceof UnsafeUrlForwardBarrierGuard
38+
guard instanceof PathTraversalBarrierGuard
3939
}
4040

4141
override DataFlow::FlowFeature getAFeature() {

java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qll

Lines changed: 0 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import java
2-
import DataFlow
3-
import semmle.code.java.controlflow.Guards
42
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
83
private 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. */
149
abstract 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`. */
2012
private 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-
23154
private class ForwardPrefix extends InterestingPrefix {
23255
ForwardPrefix() { this.getStringValue() = "forward:" }
23356

0 commit comments

Comments
 (0)