-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Python] : Cors misconfiguration #16989
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<!DOCTYPE qhelp SYSTEM "qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p> | ||
This configuration can give the attacker the ability to control <code>Access-Control-Allow-Origin</code> , and also allow the inclusion of cookies on the cross-origin request, | ||
(i.e. when the <code>Access-Control-Allow-Credentials</code> header is set to true) | ||
meaning that Peer B can send a request to Peer A that will include the cookies as if the request was executed by the user. | ||
</p> | ||
</overview> | ||
<recommendation> | ||
<p> | ||
When configuring CORS that allow credentials passing, | ||
it's best not to use user-provided values for the allowed origins response header, | ||
especially if the cookies grant session permissions on the user's account. | ||
</p> | ||
</recommendation> | ||
<example> | ||
<p> | ||
the example below shows the study case. The request header | ||
<code>origins</code> controls the allowed origins for such a | ||
Cross-Origin request. | ||
</p> | ||
|
||
<sample src="test.py" /> | ||
<references> | ||
<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> | ||
<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> | ||
<li>PortSwigger: <a href="http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html">Exploiting CORS Misconfigurations for Bitcoins and Bounties</a></li> | ||
<li>W3C: <a href="https://w3c.github.io/webappsec-cors-for-developers/#resources">CORS for developers, Advice for Resource Owners</a></li> | ||
</references> | ||
</qhelp> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/** | ||
* @name Cors misconfiguration | ||
* @description CORS header is delivered by untrusted input, allowing a remote user to control which origins are trusted. | ||
* @kind path-problem | ||
* @problem.severity error | ||
* @precision high | ||
* @id py/unvalidated-cors-origin-set | ||
* @tags security | ||
* external/cwe/cwe-346 | ||
*/ | ||
|
||
import python | ||
import experimental.semmle.python.Concepts | ||
import semmle.python.dataflow.new.TaintTracking | ||
import semmle.python.dataflow.new.DataFlow | ||
import semmle.python.dataflow.new.RemoteFlowSources | ||
import semmle.python.dataflow.new.TaintTracking2 | ||
|
||
/** | ||
* Provides the name of the `Access-Control-Allow-Origin` header key. | ||
*/ | ||
string headerAllowOrigin() { result = "Access-Control-Allow-Origin".toLowerCase() } | ||
|
||
/** | ||
* Holds if `header` sets `Access-Control-Allow-Credentials` to `true`. | ||
*/ | ||
private predicate setsAllowCredentials(HeaderDeclaration header) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
header.getNameArg().asExpr().(StrConst).getText().toLowerCase() = | ||
"access-control-allow-credentials" and | ||
header.getValueArg().asExpr().(StrConst).getText().toLowerCase() = "true" | ||
} | ||
|
||
/** | ||
* This taintflow2 configuration checks if there is a flow from source node towards CorsProbableCheckAccess methods. | ||
*/ | ||
class CorsSourceReachesCheckConfig extends TaintTracking2::Configuration { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be a more complex (and I'd say optional) change, but this second configuration could be expressed as a |
||
CorsSourceReachesCheckConfig() { this = "CorsOriginConfig" } | ||
|
||
override predicate isSource(DataFlow::Node source) { any(CorsOriginConfig c).hasFlow(source, _) } | ||
|
||
override predicate isSink(DataFlow::Node sink) { | ||
exists(Compare cmp, Expr left, Expr right, Cmpop cmpop | | ||
cmpop.getSymbol() = ["==", "in", "is not", "!="] and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||
cmp.compares(left, cmpop, right) and | ||
sink.asExpr() = [left, right] | ||
) | ||
} | ||
} | ||
|
||
private class CorsOriginConfig extends TaintTracking::Configuration { | ||
CorsOriginConfig() { this = "CorsOriginConfig" } | ||
|
||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } | ||
|
||
override predicate isSink(DataFlow::Node sink) { | ||
exists(HeaderDeclaration corsHeader, HeaderDeclaration allowCredentialsHeader | | ||
headerAllowOrigin() = corsHeader.getNameArg().asExpr().(StrConst).getText().toLowerCase() and | ||
setsAllowCredentials(allowCredentialsHeader) and | ||
corsHeader.getEnclosingCallable() = allowCredentialsHeader.getEnclosingCallable() and | ||
sink = corsHeader.getValueArg() | ||
) | ||
} | ||
} | ||
|
||
from | ||
DataFlow::PathNode source, DataFlow::PathNode sink, CorsOriginConfig conf, | ||
CorsSourceReachesCheckConfig sanconf | ||
where conf.hasFlowPath(source, sink) and not sanconf.hasFlow(source.getNode(), _) | ||
select sink.getNode(), source, sink, "CORS header is being set using user controlled value $@.", | ||
source.getNode(), "user-provided value" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
| test.py:7:55:7:64 | ControlFlowNode for rfs_header | test.py:5:18:5:24 | ControlFlowNode for request | test.py:7:55:7:64 | ControlFlowNode for rfs_header | CORS header is being set using user controlled value $@. | test.py:5:18:5:24 | ControlFlowNode for request | user-provided value | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from flask import Response, request, Flask | ||
|
||
@app.route("/flask_Response") | ||
def flask_Response(): | ||
rfs_header = request.args["rfs_header"] | ||
response = Response() | ||
response.headers['Access-Control-Allow-Origin'] = rfs_header | ||
response.headers['Access-Control-Allow-Credentials'] = "true" | ||
return response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example file appears to be missing.
(if the
test.py
file from the test directory is intended to be used as the example here, it should be copied into this directory).Additionally, the closing
</example>
tag appears to be missing.