Skip to content

Commit a64fc2b

Browse files
committed
Java: Queries to detect remote source flow to CORS header
1 parent b794fcb commit a64fc2b

12 files changed

Lines changed: 272 additions & 0 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import java.io.IOException;
2+
3+
import javax.servlet.Filter;
4+
import javax.servlet.FilterChain;
5+
import javax.servlet.FilterConfig;
6+
import javax.servlet.ServletException;
7+
import javax.servlet.ServletRequest;
8+
import javax.servlet.ServletResponse;
9+
import javax.servlet.http.HttpServletRequest;
10+
import javax.servlet.http.HttpServletResponse;
11+
12+
import org.apache.commons.lang3.StringUtils;
13+
14+
public class CorsFilter implements Filter {
15+
public void init(FilterConfig filterConfig) throws ServletException {
16+
// init
17+
}
18+
19+
public void doFilter(ServletRequest req, ServletResponse res,
20+
FilterChain chain) throws IOException, ServletException {
21+
HttpServletRequest request = (HttpServletRequest) req;
22+
HttpServletResponse response = (HttpServletResponse) res;
23+
String url = request.getHeader("Origin");
24+
25+
if (!StringUtils.isEmpty(url)) {
26+
String val = response.getHeader("Access-Control-Allow-Origin");
27+
28+
if (StringUtils.isEmpty(val)) {
29+
response.addHeader("Access-Control-Allow-Origin", url);
30+
response.addHeader("Access-Control-Allow-Credentials", "true");
31+
}
32+
}
33+
34+
chain.doFilter(req, res);
35+
}
36+
37+
public void destroy() {
38+
// destroy
39+
}
40+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
9+
A server can send the
10+
<code>"Access-Control-Allow-Credentials"</code> CORS header to control
11+
when a browser may send user credentials in Cross-Origin HTTP
12+
requests.
13+
14+
</p>
15+
<p>
16+
17+
When the <code>Access-Control-Allow-Credentials</code> header
18+
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
22+
dynamically compute the value of the
23+
<code>"Access-Control-Allow-Origin"</code> header. Computing this
24+
header value from information in the request to the server can
25+
therefore potentially allow an attacker to control the origins that
26+
the browser sends credentials to.
27+
28+
</p>
29+
30+
31+
32+
</overview>
33+
34+
<recommendation>
35+
<p>
36+
37+
When the <code>Access-Control-Allow-Credentials</code> header
38+
value is <code>"true"</code>, a dynamic computation of the
39+
<code>Access-Control-Allow-Origin</code> header must involve
40+
sanitization if it relies on user-controlled input.
41+
42+
43+
</p>
44+
<p>
45+
46+
Since the <code>"null"</code> origin is easy to obtain for an
47+
attacker, it is never safe to use <code>"null"</code> as the value of
48+
the <code>Access-Control-Allow-Origin</code> header when the
49+
<code>Access-Control-Allow-Credentials</code> header value is
50+
<code>"true"</code>.
51+
52+
</p>
53+
</recommendation>
54+
55+
<example>
56+
<p>
57+
58+
In the example below, the server allows the browser to send
59+
user credentials in a Cross-Origin request. The request header
60+
<code>origins</code> controls the allowed origins for such a
61+
Cross-Origin request.
62+
63+
</p>
64+
65+
<sample src="UnvalidatedCors.java"/>
66+
67+
</example>
68+
69+
<references>
70+
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin">CORS, Access-Control-Allow-Origin</a>.</li>
71+
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials">CORS, Access-Control-Allow-Credentials</a>.</li>
72+
<li>PortSwigger: <a href="http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html">Exploiting CORS Misconfigurations for Bitcoins and Bounties</a></li>
73+
<li>W3C: <a href="https://w3c.github.io/webappsec-cors-for-developers/#resources">CORS for developers, Advice for Resource Owners</a></li>
74+
</references>
75+
</qhelp>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @name Cors header being set from remote source
3+
* @description Cors header is being set from remote source, allowing to control the origin.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id java/unvalidated-cors-origin-set
8+
* @tags security
9+
* external/cwe/cwe-346
10+
*/
11+
12+
import java
13+
import semmle.code.java.dataflow.FlowSources
14+
import semmle.code.java.frameworks.Servlets
15+
import semmle.code.java.dataflow.TaintTracking
16+
import DataFlow::PathGraph
17+
18+
// Check for Access-Control-Allow-Credentials as well, this ensures fair chances of exploitability.
19+
predicate satisfyAllowCredentials(MethodAccess header, MethodAccess check) {
20+
header.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() =
21+
"access-control-allow-credentials" and
22+
header.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "true" and
23+
header.getEnclosingCallable() = check.getEnclosingCallable()
24+
}
25+
26+
predicate checkAccessControlAllowOriginHeader(Expr expr) {
27+
expr.(CompileTimeConstantExpr).getStringValue().toLowerCase() = "access-control-allow-origin"
28+
}
29+
30+
class CorsOriginConfig extends TaintTracking::Configuration {
31+
CorsOriginConfig() { this = "CORSOriginConfig" }
32+
33+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
34+
35+
override predicate isSink(DataFlow::Node sink) {
36+
exists(ResponseSetHeaderMethod h, MethodAccess m |
37+
m = h.getAReference() and
38+
checkAccessControlAllowOriginHeader(m.getArgument(0)) and
39+
satisfyAllowCredentials(h.getAReference(), m) and
40+
sink.asExpr() = m.getArgument(1)
41+
)
42+
or
43+
exists(ResponseAddHeaderMethod a, MethodAccess m |
44+
m = a.getAReference() and
45+
checkAccessControlAllowOriginHeader(m.getArgument(0)) and
46+
satisfyAllowCredentials(a.getAReference(), m) and
47+
sink.asExpr() = m.getArgument(1)
48+
)
49+
}
50+
}
51+
52+
from DataFlow::PathNode source, DataFlow::PathNode sink, CorsOriginConfig conf
53+
where conf.hasFlowPath(source, sink)
54+
select sink.getNode(), source, sink, "Cors header is being set using user controlled value $@.",
55+
source.getNode(), "user-provided value"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
edges
2+
| UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | UnvalidatedCors.java:40:67:40:69 | url |
3+
nodes
4+
| UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | semmle.label | getHeader(...) : String |
5+
| UnvalidatedCors.java:40:67:40:69 | url | semmle.label | url |
6+
#select
7+
| UnvalidatedCors.java:40:67:40:69 | url | UnvalidatedCors.java:34:22:34:48 | getHeader(...) : String | UnvalidatedCors.java:40:67:40:69 | url | Cors header is being set using user controlled value $@. | UnvalidatedCors.java:34:22:34:48 | getHeader(...) | user-provided value |
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.mossle.core.servlet;
2+
3+
import java.io.IOException;
4+
5+
import javax.servlet.Filter;
6+
import javax.servlet.FilterChain;
7+
import javax.servlet.FilterConfig;
8+
import javax.servlet.ServletException;
9+
import javax.servlet.ServletRequest;
10+
import javax.servlet.ServletResponse;
11+
import javax.servlet.http.HttpServletRequest;
12+
import javax.servlet.http.HttpServletResponse;
13+
14+
import org.apache.commons.lang3.StringUtils;
15+
16+
/**
17+
* <pre>
18+
* <script>
19+
* $(function () {
20+
* $.ajaxSetup({crossDomain: true, xhrFields: {withCredentials: true}});
21+
* });
22+
* </script>
23+
* </pre>
24+
*/
25+
public class UnvalidatedCors implements Filter {
26+
public void init(FilterConfig filterConfig) throws ServletException {
27+
// init
28+
}
29+
30+
public void doFilter(ServletRequest req, ServletResponse res,
31+
FilterChain chain) throws IOException, ServletException {
32+
HttpServletRequest request = (HttpServletRequest) req;
33+
HttpServletResponse response = (HttpServletResponse) res;
34+
String url = request.getHeader("Origin");
35+
36+
if (!StringUtils.isEmpty(url)) {
37+
String val = response.getHeader("Access-Control-Allow-Origin");
38+
39+
if (StringUtils.isEmpty(val)) {
40+
response.addHeader("Access-Control-Allow-Origin", url);
41+
response.addHeader("Access-Control-Allow-Credentials", "true");
42+
}
43+
}
44+
45+
chain.doFilter(req, res);
46+
}
47+
48+
public void destroy() {
49+
// destroy
50+
}
51+
}
52+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE/CWE-346/UnvalidatedCors.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/apache-commons-lang3-3.7
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.apache.commons.lang3;
2+
3+
public class StringUtils {
4+
public static boolean isEmpty(final CharSequence cs) {
5+
return cs == null || cs.length() == 0;
6+
}
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package javax.servlet;
2+
3+
import java.io.IOException;
4+
5+
public interface Filter {
6+
default public void init(FilterConfig filterConfig) throws ServletException {}
7+
public void doFilter(ServletRequest request, ServletResponse response,
8+
FilterChain chain)
9+
throws IOException, ServletException;
10+
default public void destroy() {}
11+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package javax.servlet;
2+
3+
import java.io.IOException;
4+
5+
public interface FilterChain {
6+
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
7+
}

0 commit comments

Comments
 (0)