Skip to content

Commit 45bdb22

Browse files
committed
Switch from sanitizer to tainttracking, formatting and qldoc changes
1 parent 2baf2aa commit 45bdb22

File tree

4 files changed

+46
-41
lines changed

4 files changed

+46
-41
lines changed

java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ public void doFilter(ServletRequest req, ServletResponse res,
2929
}
3030
}
3131

32+
if (!StringUtils.isEmpty(url)) {
33+
List<String> checkorigins = Arrays.asList("www.example.com", "www.sub.example.com");
34+
35+
if (checkorigins.contains(url)) { // GOOD -> Origin is validated here.
36+
response.addHeader("Access-Control-Allow-Origin", url);
37+
response.addHeader("Access-Control-Allow-Credentials", "true");
38+
}
39+
}
40+
3241
chain.doFilter(req, res);
3342
}
3443

java/ql/src/Security/CWE/CWE-346/UnvalidatedCors.qhelp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
When the <code>Access-Control-Allow-Credentials</code> header
1818
is <code>true</code>, the <code>Access-Control-Allow-Origin</code>
19-
header must have a value different from <code>*</code> in order to
20-
make browsers accept the header. Therefore, to allow multiple origins
21-
for Cross-Origin requests with credentials, the server must
19+
header must have a value different from <code>*</code> in order
20+
for browsers to accept the header. Therefore, to allow multiple origins
21+
for cross-origin requests with credentials, the server must
2222
dynamically compute the value of the
2323
<code>Access-Control-Allow-Origin</code> header. Computing this
2424
header value from information in the request to the server can
@@ -47,8 +47,8 @@
4747
attacker, it is never safe to use <code>null</code> as the value of
4848
the <code>Access-Control-Allow-Origin</code> header when the
4949
<code>Access-Control-Allow-Credentials</code> header value is
50-
<code>true</code>.This can be done using a sandboxed iframe. A more detailed
51-
explanation is available in the portswigger blogpost referenced below.
50+
<code>true</code>.A null origin can be set by an attacker using a sandboxed iframe.
51+
A more detailed explanation is available in the portswigger blogpost referenced below.
5252

5353
</p>
5454
</recommendation>
@@ -57,7 +57,7 @@
5757
<p>
5858

5959
In the example below, the server allows the browser to send
60-
user credentials in a Cross-Origin request. The request header
60+
user credentials in a cross-origin request. The request header
6161
<code>origins</code> controls the allowed origins for such a
6262
Cross-Origin request.
6363

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
2-
* @name Cors header being set from remote source
3-
* @description Cors header is being set from remote source, allowing to control the origin.
2+
* @name CORS is derived from untrusted input
3+
* @description CORS header is derived from untrusted input, allowing a remote user to control which origins are trusted.
44
* @kind path-problem
55
* @problem.severity error
66
* @precision high
@@ -13,6 +13,7 @@ import java
1313
import semmle.code.java.dataflow.FlowSources
1414
import semmle.code.java.frameworks.Servlets
1515
import semmle.code.java.dataflow.TaintTracking
16+
import semmle.code.java.dataflow.TaintTracking2
1617
import DataFlow::PathGraph
1718

1819
/**
@@ -25,10 +26,10 @@ private predicate setsAllowCredentials(MethodAccess header) {
2526
) and
2627
header.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() =
2728
"access-control-allow-credentials" and
28-
header.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true"
29+
header.getArgument(1).(CompileTimeConstantExpr).getStringValue().toLowerCase() = "true"
2930
}
3031

31-
class CorsProbableCheckAccess extends MethodAccess {
32+
private class CorsProbableCheckAccess extends MethodAccess {
3233
CorsProbableCheckAccess() {
3334
getMethod().hasName("contains") and
3435
getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType
@@ -45,46 +46,41 @@ private Expr getAccessControlAllowOriginHeaderName() {
4546
result.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin"
4647
}
4748

49+
/**
50+
* This taintflow2 configuration checks if there is a flow from source node towards CorsProbableCheckAccess methods.
51+
*/
52+
class CorsSourceReachesCheckConfig extends TaintTracking2::Configuration {
53+
CorsSourceReachesCheckConfig() { this = "CorsOriginConfig" }
54+
55+
override predicate isSource(DataFlow::Node source) { any(CorsOriginConfig c).hasFlow(source, _) }
56+
57+
override predicate isSink(DataFlow::Node sink) {
58+
sink.asExpr() = any(CorsProbableCheckAccess check).getAnArgument()
59+
}
60+
}
61+
4862
class CorsOriginConfig extends TaintTracking::Configuration {
4963
CorsOriginConfig() { this = "CorsOriginConfig" }
5064

5165
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
5266

5367
override predicate isSink(DataFlow::Node sink) {
54-
exists(MethodAccess corsheader, MethodAccess allowcredentialsheader |
68+
exists(MethodAccess corsHeader, MethodAccess allowCredentialsHeader |
5569
(
56-
corsheader.getMethod() instanceof ResponseSetHeaderMethod or
57-
corsheader.getMethod() instanceof ResponseAddHeaderMethod
70+
corsHeader.getMethod() instanceof ResponseSetHeaderMethod or
71+
corsHeader.getMethod() instanceof ResponseAddHeaderMethod
5872
) and
59-
getAccessControlAllowOriginHeaderName() = corsheader.getArgument(0) and
60-
setsAllowCredentials(allowcredentialsheader) and
61-
corsheader.getEnclosingCallable() = allowcredentialsheader.getEnclosingCallable() and
62-
sink.asExpr() = corsheader.getArgument(1)
63-
)
64-
}
65-
66-
/**
67-
* this sanitizer is oversimplistic:
68-
* - it only considers local dataflows
69-
* - it will consider any method calling `Collection.contains` or `String.equals` as a sanitizer
70-
* no matter if that check is taken into account and its result reaches the
71-
* return statement of the wrapper.
72-
*/
73-
override predicate isSanitizer(DataFlow::Node node) {
74-
exists(CorsProbableCheckAccess check |
75-
TaintTracking::localTaint(node, DataFlow::exprNode(check.getAnArgument()))
76-
or
77-
exists(MethodAccess wrapperAccess, Method wrapper, int i |
78-
TaintTracking::localTaint(node, DataFlow::exprNode(wrapperAccess.getArgument(i))) and
79-
wrapperAccess.getMethod() = wrapper and
80-
TaintTracking::localTaint(DataFlow::parameterNode(wrapper.getParameter(i)),
81-
DataFlow::exprNode(check.getAnArgument()))
82-
)
73+
getAccessControlAllowOriginHeaderName() = corsHeader.getArgument(0) and
74+
setsAllowCredentials(allowCredentialsHeader) and
75+
corsHeader.getEnclosingCallable() = allowCredentialsHeader.getEnclosingCallable() and
76+
sink.asExpr() = corsHeader.getArgument(1)
8377
)
8478
}
8579
}
8680

87-
from DataFlow::PathNode source, DataFlow::PathNode sink, CorsOriginConfig conf
88-
where conf.hasFlowPath(source, sink)
89-
select sink.getNode(), source, sink, "Cors header is being set using user controlled value $@.",
81+
from
82+
DataFlow::PathNode source, DataFlow::PathNode sink, CorsOriginConfig conf,
83+
CorsSourceReachesCheckConfig sanconf
84+
where conf.hasFlowPath(source, sink) and not sanconf.hasFlow(source.getNode(), _)
85+
select sink.getNode(), source, sink, "CORS header is being set using user controlled value $@.",
9086
source.getNode(), "user-provided value"

java/ql/test/query-tests/security/CWE-346/UnvalidatedCors.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ nodes
44
| UnvalidatedCors.java:21:22:21:48 | getHeader(...) : String | semmle.label | getHeader(...) : String |
55
| UnvalidatedCors.java:27:67:27:69 | url | semmle.label | url |
66
#select
7-
| UnvalidatedCors.java:27:67:27:69 | url | UnvalidatedCors.java:21:22:21:48 | getHeader(...) : String | UnvalidatedCors.java:27:67:27:69 | url | Cors header is being set using user controlled value $@. | UnvalidatedCors.java:21:22:21:48 | getHeader(...) | user-provided value |
7+
| UnvalidatedCors.java:27:67:27:69 | url | UnvalidatedCors.java:21:22:21:48 | getHeader(...) : String | UnvalidatedCors.java:27:67:27:69 | url | CORS header is being set using user controlled value $@. | UnvalidatedCors.java:21:22:21:48 | getHeader(...) | user-provided value |

0 commit comments

Comments
 (0)