mirror of
https://github.com/github/codeql.git
synced 2026-04-29 10:45:15 +02:00
Add query to find HTTP requests that disable SSL validation
This commit is contained in:
@@ -419,6 +419,15 @@ module HTTP {
|
|||||||
|
|
||||||
/** Gets a string that identifies the framework used for this request. */
|
/** Gets a string that identifies the framework used for this request. */
|
||||||
string getFramework() { result = super.getFramework() }
|
string getFramework() { result = super.getFramework() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this request is made using a mode that disables SSL/TLS
|
||||||
|
* certificate validation, where `disablingNode` represents the point at
|
||||||
|
* which the validation was disabled.
|
||||||
|
*/
|
||||||
|
predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
super.disablesCertificateValidation(disablingNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a class for modeling new HTTP requests. */
|
/** Provides a class for modeling new HTTP requests. */
|
||||||
@@ -435,6 +444,13 @@ module HTTP {
|
|||||||
|
|
||||||
/** Gets a string that identifies the framework used for this request. */
|
/** Gets a string that identifies the framework used for this request. */
|
||||||
abstract string getFramework();
|
abstract string getFramework();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if this request is made using a mode that disables SSL/TLS
|
||||||
|
* certificate validation, where `disablingNode` represents the point at
|
||||||
|
* which the validation was disabled.
|
||||||
|
*/
|
||||||
|
abstract predicate disablesCertificateValidation(DataFlow::Node disablingNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,13 @@ class BooleanLiteral extends Literal, TBooleanLiteral {
|
|||||||
|
|
||||||
/** Holds if the Boolean literal is `false` or `FALSE`. */
|
/** Holds if the Boolean literal is `false` or `FALSE`. */
|
||||||
predicate isFalse() { none() }
|
predicate isFalse() { none() }
|
||||||
|
|
||||||
|
/** Gets the value of this Boolean literal. */
|
||||||
|
boolean getValue() {
|
||||||
|
this.isTrue() and result = true
|
||||||
|
or
|
||||||
|
this.isFalse() and result = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TrueLiteral extends BooleanLiteral, TTrueLiteral {
|
private class TrueLiteral extends BooleanLiteral, TTrueLiteral {
|
||||||
@@ -750,7 +757,7 @@ class HashLiteral extends Literal, THashLiteral {
|
|||||||
final override string getAPrimaryQlClass() { result = "HashLiteral" }
|
final override string getAPrimaryQlClass() { result = "HashLiteral" }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the `n`th element in this array literal.
|
* Gets the `n`th element in this hash literal.
|
||||||
*
|
*
|
||||||
* In the following example, the 0th element is a `Pair`, and the 1st element
|
* In the following example, the 0th element is a `Pair`, and the 1st element
|
||||||
* is a `HashSplatExpr`.
|
* is a `HashSplatExpr`.
|
||||||
@@ -761,7 +768,7 @@ class HashLiteral extends Literal, THashLiteral {
|
|||||||
*/
|
*/
|
||||||
final Expr getElement(int n) { toGenerated(result) = g.getChild(n) }
|
final Expr getElement(int n) { toGenerated(result) = g.getChild(n) }
|
||||||
|
|
||||||
/** Gets an element in this array literal. */
|
/** Gets an element in this hash literal. */
|
||||||
final Expr getAnElement() { result = this.getElement(_) }
|
final Expr getAnElement() { result = this.getElement(_) }
|
||||||
|
|
||||||
/** Gets a key-value `Pair` in this hash literal. */
|
/** Gets a key-value `Pair` in this hash literal. */
|
||||||
|
|||||||
@@ -18,29 +18,113 @@ private import codeql.ruby.ApiGraphs
|
|||||||
* https://github.com/excon/excon/blob/master/README.md
|
* https://github.com/excon/excon/blob/master/README.md
|
||||||
*/
|
*/
|
||||||
class ExconHttpRequest extends HTTP::Client::Request::Range {
|
class ExconHttpRequest extends HTTP::Client::Request::Range {
|
||||||
DataFlow::Node request;
|
DataFlow::Node requestUse;
|
||||||
DataFlow::CallNode responseBody;
|
API::Node requestNode;
|
||||||
|
API::Node connectionNode;
|
||||||
|
|
||||||
ExconHttpRequest() {
|
ExconHttpRequest() {
|
||||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
requestUse = requestNode.getAnImmediateUse() and
|
||||||
requestNode =
|
connectionNode =
|
||||||
[
|
[
|
||||||
// one-off requests
|
// one-off requests
|
||||||
API::getTopLevelMember("Excon"),
|
API::getTopLevelMember("Excon"),
|
||||||
// connection re-use
|
// connection re-use
|
||||||
API::getTopLevelMember("Excon").getInstance()
|
API::getTopLevelMember("Excon").getInstance(),
|
||||||
]
|
API::getTopLevelMember("Excon").getMember("Connection").getInstance()
|
||||||
.getReturn([
|
] and
|
||||||
// Excon#request exists but Excon.request doesn't.
|
requestNode =
|
||||||
// This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
|
connectionNode
|
||||||
"get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
|
.getReturn([
|
||||||
]) and
|
// Excon#request exists but Excon.request doesn't.
|
||||||
responseBody = requestNode.getAMethodCall("body") and
|
// This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
|
||||||
this = request.asExpr().getExpr()
|
"get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
|
||||||
|
]) and
|
||||||
|
this = requestUse.asExpr().getExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
// Check for `ssl_verify_peer: false` in the options hash.
|
||||||
|
exists(DataFlow::Node arg, int i |
|
||||||
|
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||||
|
|
|
||||||
|
argSetsVerifyPeer(arg, false, disablingNode)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Or we see a call to `Excon.defaults[:ssl_verify_peer] = false` before the
|
||||||
|
// request, and no `ssl_verify_peer: true` in the explicit options hash for
|
||||||
|
// the request call.
|
||||||
|
exists(DataFlow::CallNode disableCall |
|
||||||
|
setsDefaultVerification(disableCall, false) and
|
||||||
|
disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and
|
||||||
|
disablingNode = disableCall and
|
||||||
|
not exists(DataFlow::Node arg, int i |
|
||||||
|
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||||
|
|
|
||||||
|
argSetsVerifyPeer(arg, true, _)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
|
||||||
|
|
||||||
override string getFramework() { result = "Excon" }
|
override string getFramework() { result = "Excon" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `arg` represents an options hash that contains the key
|
||||||
|
* `:ssl_verify_peer` with `value`, where `kvNode` is the data-flow node for
|
||||||
|
* this key-value pair.
|
||||||
|
*/
|
||||||
|
predicate argSetsVerifyPeer(DataFlow::Node arg, boolean value, DataFlow::Node kvNode) {
|
||||||
|
// Either passed as an individual key:value argument, e.g.:
|
||||||
|
// Excon.get(..., ssl_verify_peer: false)
|
||||||
|
isSslVerifyPeerPair(arg.asExpr().getExpr(), value) and
|
||||||
|
kvNode = arg
|
||||||
|
or
|
||||||
|
// Or as a single hash argument, e.g.:
|
||||||
|
// Excon.get(..., { ssl_verify_peer: false, ... })
|
||||||
|
exists(DataFlow::LocalSourceNode optionsNode, Pair p |
|
||||||
|
p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
|
||||||
|
isSslVerifyPeerPair(p, value) and
|
||||||
|
optionsNode.flowsTo(arg) and
|
||||||
|
kvNode.asExpr().getExpr() = p
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `callNode` sets `Excon.defaults[:ssl_verify_peer]` or
|
||||||
|
* `Excon.ssl_verify_peer` to `value`.
|
||||||
|
*/
|
||||||
|
private predicate setsDefaultVerification(DataFlow::CallNode callNode, boolean value) {
|
||||||
|
callNode = API::getTopLevelMember("Excon").getReturn("defaults").getAMethodCall("[]=") and
|
||||||
|
isSslVerifyPeerLiteral(callNode.getArgument(0)) and
|
||||||
|
hasBooleanValue(callNode.getArgument(1), value)
|
||||||
|
or
|
||||||
|
callNode = API::getTopLevelMember("Excon").getAMethodCall("ssl_verify_peer=") and
|
||||||
|
hasBooleanValue(callNode.getArgument(0), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "ssl_verify_peer" and
|
||||||
|
literal.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `node` can contain `value`. */
|
||||||
|
private predicate hasBooleanValue(DataFlow::Node node, boolean value) {
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(BooleanLiteral).getValue() = value and
|
||||||
|
literal.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `p` is the pair `ssl_verify_peer: <value>`. */
|
||||||
|
private predicate isSslVerifyPeerPair(Pair p, boolean value) {
|
||||||
|
exists(DataFlow::Node key, DataFlow::Node valueNode |
|
||||||
|
key.asExpr().getExpr() = p.getKey() and valueNode.asExpr().getExpr() = p.getValue()
|
||||||
|
|
|
||||||
|
isSslVerifyPeerLiteral(key) and
|
||||||
|
hasBooleanValue(valueNode, value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,25 +14,131 @@ private import codeql.ruby.ApiGraphs
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
class FaradayHttpRequest extends HTTP::Client::Request::Range {
|
||||||
DataFlow::Node request;
|
DataFlow::Node requestUse;
|
||||||
DataFlow::CallNode responseBody;
|
API::Node requestNode;
|
||||||
|
API::Node connectionNode;
|
||||||
|
|
||||||
FaradayHttpRequest() {
|
FaradayHttpRequest() {
|
||||||
exists(API::Node requestNode |
|
connectionNode =
|
||||||
requestNode =
|
[
|
||||||
[
|
// one-off requests
|
||||||
// one-off requests
|
API::getTopLevelMember("Faraday"),
|
||||||
API::getTopLevelMember("Faraday"),
|
// connection re-use
|
||||||
// connection re-use
|
API::getTopLevelMember("Faraday").getInstance()
|
||||||
API::getTopLevelMember("Faraday").getInstance()
|
] and
|
||||||
].getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
|
requestNode =
|
||||||
responseBody = requestNode.getAMethodCall("body") and
|
connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
|
||||||
request = requestNode.getAnImmediateUse() and
|
requestUse = requestNode.getAnImmediateUse() and
|
||||||
this = request.asExpr().getExpr()
|
this = requestUse.asExpr().getExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
// `Faraday::new` takes an options hash as its second argument, and we're
|
||||||
|
// looking for
|
||||||
|
// `{ ssl: { verify: false } }`
|
||||||
|
// or
|
||||||
|
// `{ ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }`
|
||||||
|
exists(DataFlow::Node arg, int i |
|
||||||
|
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||||
|
|
|
||||||
|
// Either passed as an individual key:value argument, e.g.:
|
||||||
|
// Faraday.new(..., ssl: {...})
|
||||||
|
isSslOptionsPairDisablingValidation(arg.asExpr().getExpr()) and
|
||||||
|
disablingNode = arg
|
||||||
|
or
|
||||||
|
// Or as a single hash argument, e.g.:
|
||||||
|
// Faraday.new(..., { ssl: {...} })
|
||||||
|
exists(DataFlow::LocalSourceNode optionsNode, Pair p |
|
||||||
|
p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
|
||||||
|
isSslOptionsPairDisablingValidation(p) and
|
||||||
|
optionsNode.flowsTo(arg) and
|
||||||
|
disablingNode.asExpr().getExpr() = p
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
|
||||||
|
|
||||||
override string getFramework() { result = "Faraday" }
|
override string getFramework() { result = "Faraday" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the pair `p` contains the key `:ssl` for which the value is a hash
|
||||||
|
* containing either `verify: false` or
|
||||||
|
* `verify_mode: OpenSSL::SSL::VERIFY_NONE`.
|
||||||
|
*/
|
||||||
|
private predicate isSslOptionsPairDisablingValidation(Pair p) {
|
||||||
|
exists(DataFlow::Node key, DataFlow::Node value |
|
||||||
|
key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
|
||||||
|
|
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "ssl" and
|
||||||
|
literal.flowsTo(key)
|
||||||
|
) and
|
||||||
|
(isHashWithVerifyFalse(value) or isHashWithVerifyModeNone(value))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `node` represents a hash containing the key-value pair
|
||||||
|
* `verify: false`.
|
||||||
|
*/
|
||||||
|
private predicate isHashWithVerifyFalse(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode hash |
|
||||||
|
isVerifyFalsePair(hash.asExpr().getExpr().(HashLiteral).getAKeyValuePair()) and
|
||||||
|
hash.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `node` represents a hash containing the key-value pair
|
||||||
|
* `verify_mode: OpenSSL::SSL::VERIFY_NONE`.
|
||||||
|
*/
|
||||||
|
private predicate isHashWithVerifyModeNone(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode hash |
|
||||||
|
isVerifyModeNonePair(hash.asExpr().getExpr().(HashLiteral).getAKeyValuePair()) and
|
||||||
|
hash.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the pair `p` has the key `:verify_mode` and the value
|
||||||
|
* `OpenSSL::SSL::VERIFY_NONE`.
|
||||||
|
*/
|
||||||
|
private predicate isVerifyModeNonePair(Pair p) {
|
||||||
|
exists(DataFlow::Node key, DataFlow::Node value |
|
||||||
|
key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
|
||||||
|
|
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "verify_mode" and
|
||||||
|
literal.flowsTo(key)
|
||||||
|
) and
|
||||||
|
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the pair `p` has the key `:verify` and the value `false`.
|
||||||
|
*/
|
||||||
|
private predicate isVerifyFalsePair(Pair p) {
|
||||||
|
exists(DataFlow::Node key, DataFlow::Node value |
|
||||||
|
key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
|
||||||
|
|
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "verify" and
|
||||||
|
literal.flowsTo(key)
|
||||||
|
) and
|
||||||
|
isFalsey(value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `node` contains `0` or `false`. */
|
||||||
|
private predicate isFalsey(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
(
|
||||||
|
literal.asExpr().getExpr().(BooleanLiteral).isFalse() or
|
||||||
|
literal.asExpr().getExpr().(IntegerLiteral).getValue() = 0
|
||||||
|
) and
|
||||||
|
literal.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,31 +10,46 @@ private import codeql.ruby.ApiGraphs
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class HttpClientRequest extends HTTP::Client::Request::Range {
|
class HttpClientRequest extends HTTP::Client::Request::Range {
|
||||||
DataFlow::Node request;
|
API::Node requestNode;
|
||||||
DataFlow::CallNode responseBody;
|
API::Node connectionNode;
|
||||||
|
DataFlow::Node requestUse;
|
||||||
|
string method;
|
||||||
|
|
||||||
HttpClientRequest() {
|
HttpClientRequest() {
|
||||||
exists(API::Node requestNode, string method |
|
connectionNode =
|
||||||
request = requestNode.getAnImmediateUse() and
|
[
|
||||||
method in [
|
// One-off requests
|
||||||
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
|
API::getTopLevelMember("HTTPClient"),
|
||||||
]
|
// Conncection re-use
|
||||||
|
|
API::getTopLevelMember("HTTPClient").getInstance()
|
||||||
requestNode = API::getTopLevelMember("HTTPClient").getReturn(method) and
|
] and
|
||||||
(
|
requestNode = connectionNode.getReturn(method) and
|
||||||
// The `get_content` and `post_content` methods return the response body as a string.
|
requestUse = requestNode.getAnImmediateUse() and
|
||||||
// The other methods return a `HTTPClient::Message` object which has various methods
|
method in [
|
||||||
// that return the response body.
|
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
|
||||||
method in ["get_content", "post_content"] and responseBody = request
|
] and
|
||||||
or
|
this = requestUse.asExpr().getExpr()
|
||||||
not method in ["get_content", "put_content"] and
|
|
||||||
responseBody = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])
|
|
||||||
) and
|
|
||||||
this = request.asExpr().getExpr()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
override DataFlow::Node getResponseBody() {
|
||||||
|
// The `get_content` and `post_content` methods return the response body as
|
||||||
|
// a string. The other methods return a `HTTPClient::Message` object which
|
||||||
|
// has various methods that return the response body.
|
||||||
|
method in ["get_content", "post_content"] and result = requestUse
|
||||||
|
or
|
||||||
|
not method in ["get_content", "put_content"] and
|
||||||
|
result = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
// Look for calls to set
|
||||||
|
// `c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE`
|
||||||
|
// on an HTTPClient connection object `c`.
|
||||||
|
disablingNode =
|
||||||
|
connectionNode.getReturn("ssl_config").getReturn("verify_mode=").getAnImmediateUse() and
|
||||||
|
disablingNode.(DataFlow::CallNode).getArgument(0) =
|
||||||
|
API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||||
|
}
|
||||||
|
|
||||||
override string getFramework() { result = "HTTPClient" }
|
override string getFramework() { result = "HTTPClient" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,30 +17,82 @@ private import codeql.ruby.ApiGraphs
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class HttpartyRequest extends HTTP::Client::Request::Range {
|
class HttpartyRequest extends HTTP::Client::Request::Range {
|
||||||
DataFlow::Node request;
|
API::Node requestNode;
|
||||||
DataFlow::CallNode responseBody;
|
DataFlow::Node requestUse;
|
||||||
|
|
||||||
HttpartyRequest() {
|
HttpartyRequest() {
|
||||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
requestUse = requestNode.getAnImmediateUse() and
|
||||||
requestNode =
|
requestNode =
|
||||||
API::getTopLevelMember("HTTParty")
|
API::getTopLevelMember("HTTParty")
|
||||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||||
(
|
this = requestUse.asExpr().getExpr()
|
||||||
// If HTTParty can recognise the response type, it will parse and return it
|
}
|
||||||
// directly from the request call. Otherwise, it will return a `HTTParty::Response`
|
|
||||||
// object that has a `#body` method.
|
override DataFlow::Node getResponseBody() {
|
||||||
// So if there's a call to `#body` on the response, treat that as the response body.
|
// If HTTParty can recognise the response type, it will parse and return it
|
||||||
exists(DataFlow::Node r | r = requestNode.getAMethodCall("body") | responseBody = r)
|
// directly from the request call. Otherwise, it will return a `HTTParty::Response`
|
||||||
or
|
// object that has a `#body` method.
|
||||||
// Otherwise, treat the response as the response body.
|
// So if there's a call to `#body` on the response, treat that as the response body.
|
||||||
not exists(DataFlow::Node r | r = requestNode.getAMethodCall("body")) and
|
exists(DataFlow::Node r | r = requestNode.getAMethodCall("body") | result = r)
|
||||||
responseBody = request
|
or
|
||||||
) and
|
// Otherwise, treat the response as the response body.
|
||||||
this = request.asExpr().getExpr()
|
not exists(DataFlow::Node r | r = requestNode.getAMethodCall("body")) and
|
||||||
|
result = requestUse
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
// The various request methods take an options hash as their second
|
||||||
|
// argument, and we're looking for `{ verify: false }` or
|
||||||
|
// `{ verify_peer: // false }`.
|
||||||
|
exists(DataFlow::Node arg, int i |
|
||||||
|
i > 0 and arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
|
||||||
|
|
|
||||||
|
// Either passed as an individual key:value argument, e.g.:
|
||||||
|
// HTTParty.get(..., verify: false)
|
||||||
|
isVerifyFalsePair(arg.asExpr().getExpr()) and
|
||||||
|
disablingNode = arg
|
||||||
|
or
|
||||||
|
// Or as a single hash argument, e.g.:
|
||||||
|
// HTTParty.get(..., { verify: false, ... })
|
||||||
|
exists(DataFlow::LocalSourceNode optionsNode, Pair p |
|
||||||
|
p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
|
||||||
|
isVerifyFalsePair(p) and
|
||||||
|
optionsNode.flowsTo(arg) and
|
||||||
|
disablingNode.asExpr().getExpr() = p
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
|
||||||
|
|
||||||
override string getFramework() { result = "HTTParty" }
|
override string getFramework() { result = "HTTParty" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds if `node` represents the symbol literal `verify` or `verify_peer`. */
|
||||||
|
private predicate isVerifyLiteral(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(SymbolLiteral).getValueText() = ["verify", "verify_peer"] and
|
||||||
|
literal.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `node` contains `0` or `false`. */
|
||||||
|
private predicate isFalsey(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
(
|
||||||
|
literal.asExpr().getExpr().(BooleanLiteral).isFalse() or
|
||||||
|
literal.asExpr().getExpr().(IntegerLiteral).getValue() = 0
|
||||||
|
) and
|
||||||
|
literal.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if `p` is the pair `verify: false` or `verify_peer: false`.
|
||||||
|
*/
|
||||||
|
private predicate isVerifyFalsePair(Pair p) {
|
||||||
|
exists(DataFlow::Node key, DataFlow::Node value |
|
||||||
|
key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
|
||||||
|
|
|
||||||
|
isVerifyLiteral(key) and
|
||||||
|
isFalsey(value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,5 +50,20 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
|
|||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
override DataFlow::Node getResponseBody() { result = responseBody }
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
// A Net::HTTP request bypasses certificate validation if we see a setter
|
||||||
|
// call like this:
|
||||||
|
// foo.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
// and then the receiver of that call flows to the receiver in the request:
|
||||||
|
// foo.request(...)
|
||||||
|
exists(DataFlow::CallNode setter |
|
||||||
|
disablingNode =
|
||||||
|
API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() and
|
||||||
|
setter.asExpr().getExpr().(SetterMethodCall).getMethodName() = "verify_mode=" and
|
||||||
|
disablingNode = setter.getArgument(0) and
|
||||||
|
localFlow(setter.getReceiver(), request.getReceiver())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override string getFramework() { result = "Net::HTTP" }
|
override string getFramework() { result = "Net::HTTP" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,36 +4,110 @@ private import codeql.ruby.ApiGraphs
|
|||||||
private import codeql.ruby.frameworks.StandardLibrary
|
private import codeql.ruby.frameworks.StandardLibrary
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A call that makes an HTTP request using `OpenURI`.
|
* A call that makes an HTTP request using `OpenURI` via `URI.open` or
|
||||||
|
* `URI.parse(...).open`.
|
||||||
|
*
|
||||||
* ```ruby
|
* ```ruby
|
||||||
* Kernel.open("http://example.com").read
|
|
||||||
* URI.open("http://example.com").readlines
|
* URI.open("http://example.com").readlines
|
||||||
* URI.parse("http://example.com").open.read
|
* URI.parse("http://example.com").open.read
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class OpenURIRequest extends HTTP::Client::Request::Range {
|
class OpenUriRequest extends HTTP::Client::Request::Range {
|
||||||
DataFlow::Node request;
|
API::Node requestNode;
|
||||||
DataFlow::CallNode responseBody;
|
DataFlow::Node requestUse;
|
||||||
|
|
||||||
OpenURIRequest() {
|
OpenUriRequest() {
|
||||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
requestNode =
|
||||||
requestNode =
|
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
|
||||||
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
|
.getReturn("open") and
|
||||||
.getReturn("open") and
|
requestUse = requestNode.getAnImmediateUse() and
|
||||||
responseBody = requestNode.getAMethodCall(["read", "readlines"]) and
|
this = requestUse.asExpr().getExpr()
|
||||||
this = request.asExpr().getExpr()
|
|
||||||
)
|
|
||||||
or
|
|
||||||
// Kernel.open("http://example.com").read
|
|
||||||
// open("http://example.com").read
|
|
||||||
request instanceof KernelMethodCall and
|
|
||||||
this.getMethodName() = "open" and
|
|
||||||
request.asExpr().getExpr() = this and
|
|
||||||
responseBody.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
|
|
||||||
request.(DataFlow::LocalSourceNode).flowsTo(responseBody.getReceiver())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
override DataFlow::Node getResponseBody() {
|
||||||
|
result = requestNode.getAMethodCall(["read", "readlines"])
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
exists(DataFlow::Node arg |
|
||||||
|
arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(_)
|
||||||
|
|
|
||||||
|
argumentDisablesValidation(arg, disablingNode)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override string getFramework() { result = "OpenURI" }
|
override string getFramework() { result = "OpenURI" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A call that makes an HTTP request using `OpenURI` and its `Kernel.open`
|
||||||
|
* interface.
|
||||||
|
*
|
||||||
|
* ```ruby
|
||||||
|
* Kernel.open("http://example.com").read
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range {
|
||||||
|
DataFlow::Node requestUse;
|
||||||
|
|
||||||
|
OpenUriKernelOpenRequest() {
|
||||||
|
requestUse instanceof KernelMethodCall and
|
||||||
|
this.getMethodName() = "open" and
|
||||||
|
this = requestUse.asExpr().getExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::CallNode getResponseBody() {
|
||||||
|
result.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
|
||||||
|
requestUse.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
exists(DataFlow::Node arg, int i |
|
||||||
|
i > 0 and
|
||||||
|
arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
|
||||||
|
|
|
||||||
|
argumentDisablesValidation(arg, disablingNode)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override string getFramework() { result = "OpenURI" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds if the argument `arg` is an options hash that disables certificate
|
||||||
|
* validation, and `disablingNode` is the specific node representing the
|
||||||
|
* `ssl_verify_mode: OpenSSL::SSL_VERIFY_NONE` pair.
|
||||||
|
*/
|
||||||
|
private predicate argumentDisablesValidation(DataFlow::Node arg, DataFlow::Node disablingNode) {
|
||||||
|
// Either passed as an individual key:value argument, e.g.:
|
||||||
|
// URI.open(..., ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
||||||
|
isSslVerifyModeNonePair(arg.asExpr().getExpr()) and
|
||||||
|
disablingNode = arg
|
||||||
|
or
|
||||||
|
// Or as a single hash argument, e.g.:
|
||||||
|
// URI.open(..., { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, ... })
|
||||||
|
exists(DataFlow::LocalSourceNode optionsNode, Pair p |
|
||||||
|
p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
|
||||||
|
isSslVerifyModeNonePair(p) and
|
||||||
|
optionsNode.flowsTo(arg) and
|
||||||
|
disablingNode.asExpr().getExpr() = p
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `p` is the pair `ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE`. */
|
||||||
|
private predicate isSslVerifyModeNonePair(Pair p) {
|
||||||
|
exists(DataFlow::Node key, DataFlow::Node value |
|
||||||
|
key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
|
||||||
|
|
|
||||||
|
isSslVerifyModeLiteral(key) and
|
||||||
|
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `node` can represent the symbol literal `:ssl_verify_mode`. */
|
||||||
|
private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "ssl_verify_mode" and
|
||||||
|
literal.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,21 +9,63 @@ private import codeql.ruby.ApiGraphs
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
class RestClientHttpRequest extends HTTP::Client::Request::Range {
|
||||||
DataFlow::Node request;
|
DataFlow::Node requestUse;
|
||||||
DataFlow::CallNode responseBody;
|
API::Node requestNode;
|
||||||
|
API::Node connectionNode;
|
||||||
|
|
||||||
RestClientHttpRequest() {
|
RestClientHttpRequest() {
|
||||||
exists(API::Node requestNode |
|
connectionNode =
|
||||||
requestNode =
|
[
|
||||||
API::getTopLevelMember("RestClient")
|
API::getTopLevelMember("RestClient"),
|
||||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
API::getTopLevelMember("RestClient").getMember("Resource").getInstance()
|
||||||
request = requestNode.getAnImmediateUse() and
|
] and
|
||||||
responseBody = requestNode.getAMethodCall("body") and
|
requestNode =
|
||||||
this = request.asExpr().getExpr()
|
connectionNode.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||||
|
requestUse = requestNode.getAnImmediateUse() and
|
||||||
|
this = requestUse.asExpr().getExpr()
|
||||||
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
// `RestClient::Resource::new` takes an options hash argument, and we're
|
||||||
|
// looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`.
|
||||||
|
exists(DataFlow::Node arg, int i |
|
||||||
|
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
|
||||||
|
|
|
||||||
|
// Either passed as an individual key:value argument, e.g.:
|
||||||
|
// RestClient::Resource.new(..., verify_ssl: OpenSSL::SSL::VERIFY_NONE)
|
||||||
|
isVerifySslNonePair(arg.asExpr().getExpr()) and
|
||||||
|
disablingNode = arg
|
||||||
|
or
|
||||||
|
// Or as a single hash argument, e.g.:
|
||||||
|
// RestClient::Resource.new(..., { verify_ssl: OpenSSL::SSL::VERIFY_NONE })
|
||||||
|
exists(DataFlow::LocalSourceNode optionsNode, Pair p |
|
||||||
|
p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
|
||||||
|
isVerifySslNonePair(p) and
|
||||||
|
optionsNode.flowsTo(arg) and
|
||||||
|
disablingNode.asExpr().getExpr() = p
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
|
||||||
|
|
||||||
override string getFramework() { result = "RestClient" }
|
override string getFramework() { result = "RestClient" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds if `p` is the pair `verify_ssl: OpenSSL::SSL::VERIFY_NONE`. */
|
||||||
|
private predicate isVerifySslNonePair(Pair p) {
|
||||||
|
exists(DataFlow::Node key, DataFlow::Node value |
|
||||||
|
key.asExpr().getExpr() = p.getKey() and value.asExpr().getExpr() = p.getValue()
|
||||||
|
|
|
||||||
|
isSslVerifyModeLiteral(key) and
|
||||||
|
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds if `node` can represent the symbol literal `:verify_ssl`. */
|
||||||
|
private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
|
||||||
|
exists(DataFlow::LocalSourceNode literal |
|
||||||
|
literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "verify_ssl" and
|
||||||
|
literal.flowsTo(node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,20 +9,52 @@ private import codeql.ruby.ApiGraphs
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
|
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
|
||||||
DataFlow::Node request;
|
DataFlow::Node requestUse;
|
||||||
DataFlow::CallNode responseBody;
|
API::Node requestNode;
|
||||||
|
|
||||||
TyphoeusHttpRequest() {
|
TyphoeusHttpRequest() {
|
||||||
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
|
requestUse = requestNode.getAnImmediateUse() and
|
||||||
requestNode =
|
requestNode =
|
||||||
API::getTopLevelMember("Typhoeus")
|
API::getTopLevelMember("Typhoeus")
|
||||||
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
|
||||||
responseBody = requestNode.getAMethodCall("body") and
|
this = requestUse.asExpr().getExpr()
|
||||||
this = request.asExpr().getExpr()
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
|
||||||
|
|
||||||
|
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
|
||||||
|
// Check for `ssl_verifypeer: false` in the options hash.
|
||||||
|
exists(DataFlow::Node arg, int i |
|
||||||
|
i > 0 and arg.asExpr().getExpr() = requestUse.asExpr().getExpr().(MethodCall).getArgument(i)
|
||||||
|
|
|
||||||
|
// Either passed as an individual key:value argument, e.g.:
|
||||||
|
// Typhoeus.get(..., ssl_verifypeer: false)
|
||||||
|
isSslVerifyPeerFalsePair(arg.asExpr().getExpr()) and
|
||||||
|
disablingNode = arg
|
||||||
|
or
|
||||||
|
// Or as a single hash argument, e.g.:
|
||||||
|
// Typhoeus.get(..., { ssl_verifypeer: false, ... })
|
||||||
|
exists(DataFlow::LocalSourceNode optionsNode, Pair p |
|
||||||
|
p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
|
||||||
|
isSslVerifyPeerFalsePair(p) and
|
||||||
|
optionsNode.flowsTo(arg) and
|
||||||
|
disablingNode.asExpr().getExpr() = p
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override DataFlow::Node getResponseBody() { result = responseBody }
|
|
||||||
|
|
||||||
override string getFramework() { result = "Typhoeus" }
|
override string getFramework() { result = "Typhoeus" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Holds if `p` is the pair `ssl_verifypeer: false`.
|
||||||
|
private predicate isSslVerifyPeerFalsePair(Pair p) {
|
||||||
|
p.getKey().(SymbolLiteral).getValueText() = "ssl_verifypeer" and
|
||||||
|
exists(DataFlow::LocalSourceNode literal, DataFlow::Node value |
|
||||||
|
(
|
||||||
|
literal.asExpr().getExpr().(BooleanLiteral).isFalse() or
|
||||||
|
literal.asExpr().getExpr().(IntegerLiteral).getValue() = 0
|
||||||
|
) and
|
||||||
|
literal.flowsTo(value) and
|
||||||
|
value.asExpr().getExpr() = p.getValue()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE qhelp PUBLIC
|
||||||
|
"-//Semmle//qhelp//EN"
|
||||||
|
"qhelp.dtd">
|
||||||
|
<qhelp>
|
||||||
|
<overview>
|
||||||
|
<p>
|
||||||
|
Certificate validation is the standard authentication method of a secure TLS
|
||||||
|
connection. Without it, there is no guarantee about who the other party of a TLS
|
||||||
|
connection is, making man-in-the-middle attacks more likely to occur.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
When testing software that uses TLS connections, it may be useful to
|
||||||
|
disable the certificate validation temporarily. But disabling it in
|
||||||
|
production environments is strongly discouraged, unless an alternative
|
||||||
|
method of authentication is used.
|
||||||
|
</p>
|
||||||
|
</overview>
|
||||||
|
|
||||||
|
<recommendation>
|
||||||
|
<p>
|
||||||
|
Do not disable certificate validation for TLS connections.
|
||||||
|
</p>
|
||||||
|
</recommendation>
|
||||||
|
|
||||||
|
<example>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The following example shows an HTTPS connection that makes a GET request to a
|
||||||
|
remote server. But the connection is not secure since the
|
||||||
|
<code>verify_mode</code> option of the connection is set to
|
||||||
|
<code>OpenSSL::SSL::VERIFY_NONE</code>. As a consequence, anyone can impersonate
|
||||||
|
the remote server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<sample src="examples/RequestWithoutValidation.rb"/>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To make the connection secure, the <code>verify_mode</code> option should have
|
||||||
|
its default value, or be explicitly set to
|
||||||
|
<code>OpenSSL::SSL::VERIFY_PEER</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</example>
|
||||||
|
|
||||||
|
<references>
|
||||||
|
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">Transport Layer Security (TLS)</a></li>
|
||||||
|
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">Man-in-the-middle attack</a></li>
|
||||||
|
<li>Ruby-doc: <a href="https://ruby-doc.org/stdlib-3.0.2/libdoc/net/http/rdoc/Net/HTTP.html">Net::HTTP</a></li>
|
||||||
|
</references>
|
||||||
|
|
||||||
|
</qhelp>
|
||||||
20
ql/src/queries/security/cwe-295/RequestWithoutValidation.ql
Normal file
20
ql/src/queries/security/cwe-295/RequestWithoutValidation.ql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @name Request without certificate validation
|
||||||
|
* @description Making a request without certificate validation can allow
|
||||||
|
* man-in-the-middle attacks.
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity warning
|
||||||
|
* @security-severity 7.5
|
||||||
|
* @precision medium
|
||||||
|
* @id rb/request-without-cert-validation
|
||||||
|
* @tags security
|
||||||
|
* external/cwe/cwe-295
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ruby
|
||||||
|
import codeql.ruby.Concepts
|
||||||
|
import codeql.ruby.DataFlow
|
||||||
|
|
||||||
|
from HTTP::Client::Request request, DataFlow::Node disablingNode
|
||||||
|
where request.disablesCertificateValidation(disablingNode)
|
||||||
|
select request, "This request $@.", disablingNode, "does not validate certificates"
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
require "net/https"
|
||||||
|
require "uri"
|
||||||
|
|
||||||
|
uri = URI.parse "https://example.com/"
|
||||||
|
http = Net::HTTP.new uri.host, uri.port
|
||||||
|
http.use_ssl = true
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
request = Net::HTTP::Get.new uri.request_uri
|
||||||
|
puts http.request(request).body
|
||||||
@@ -6,5 +6,7 @@
|
|||||||
| Excon.rb:18:9:18:41 | call to head | Excon.rb:19:1:19:10 | call to body |
|
| Excon.rb:18:9:18:41 | call to head | Excon.rb:19:1:19:10 | call to body |
|
||||||
| Excon.rb:21:9:21:44 | call to options | Excon.rb:22:1:22:10 | call to body |
|
| Excon.rb:21:9:21:44 | call to options | Excon.rb:22:1:22:10 | call to body |
|
||||||
| Excon.rb:24:9:24:42 | call to trace | Excon.rb:25:1:25:10 | call to body |
|
| Excon.rb:24:9:24:42 | call to trace | Excon.rb:25:1:25:10 | call to body |
|
||||||
| Excon.rb:28:9:28:33 | call to get | Excon.rb:29:1:29:10 | call to body |
|
| Excon.rb:28:9:28:34 | call to get | Excon.rb:29:1:29:10 | call to body |
|
||||||
| Excon.rb:31:10:31:38 | call to post | Excon.rb:32:1:32:11 | call to body |
|
| Excon.rb:31:10:31:39 | call to post | Excon.rb:32:1:32:11 | call to body |
|
||||||
|
| Excon.rb:35:9:35:34 | call to get | Excon.rb:36:1:36:10 | call to body |
|
||||||
|
| Excon.rb:38:10:38:39 | call to post | Excon.rb:39:1:39:11 | call to body |
|
||||||
|
|||||||
@@ -24,9 +24,16 @@ resp7.body
|
|||||||
resp8 = Excon.trace("http://example.com/")
|
resp8 = Excon.trace("http://example.com/")
|
||||||
resp8.body
|
resp8.body
|
||||||
|
|
||||||
connection = Excon.new("http://example.com")
|
connection1 = Excon.new("http://example.com")
|
||||||
resp9 = connection.get(path: "/")
|
resp9 = connection1.get(path: "/")
|
||||||
resp9.body
|
resp9.body
|
||||||
|
|
||||||
resp10 = connection.post(path: "/foo")
|
resp10 = connection1.post(path: "/foo")
|
||||||
|
resp10.body
|
||||||
|
|
||||||
|
connection2 = Excon::Connection.new("http://example.com")
|
||||||
|
resp9 = connection2.get(path: "/")
|
||||||
|
resp9.body
|
||||||
|
|
||||||
|
resp10 = connection2.post(path: "/foo")
|
||||||
resp10.body
|
resp10.body
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
| OpenURI.rb:3:9:3:41 | call to open | OpenURI.rb:4:1:4:10 | call to read |
|
openUriRequests
|
||||||
| OpenURI.rb:6:9:6:34 | call to open | OpenURI.rb:7:1:7:15 | call to readlines |
|
|
||||||
| OpenURI.rb:9:9:9:38 | call to open | OpenURI.rb:10:1:10:10 | call to read |
|
| OpenURI.rb:9:9:9:38 | call to open | OpenURI.rb:10:1:10:10 | call to read |
|
||||||
| OpenURI.rb:12:9:12:45 | call to open | OpenURI.rb:13:1:13:10 | call to read |
|
| OpenURI.rb:12:9:12:45 | call to open | OpenURI.rb:13:1:13:10 | call to read |
|
||||||
|
openUriKernelOpenRequests
|
||||||
|
| OpenURI.rb:3:9:3:41 | call to open | OpenURI.rb:4:1:4:10 | call to read |
|
||||||
|
| OpenURI.rb:6:9:6:34 | call to open | OpenURI.rb:7:1:7:15 | call to readlines |
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import codeql.ruby.frameworks.http_clients.OpenURI
|
import codeql.ruby.frameworks.http_clients.OpenURI
|
||||||
import codeql.ruby.DataFlow
|
import codeql.ruby.DataFlow
|
||||||
|
|
||||||
query DataFlow::Node openURIRequests(OpenURIRequest e) { result = e.getResponseBody() }
|
query DataFlow::Node openUriRequests(OpenUriRequest e) { result = e.getResponseBody() }
|
||||||
|
|
||||||
|
query DataFlow::Node openUriKernelOpenRequests(OpenUriKernelOpenRequest e) {
|
||||||
|
result = e.getResponseBody()
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,3 +5,4 @@
|
|||||||
| RestClient.rb:15:9:15:47 | call to delete | RestClient.rb:16:1:16:10 | call to body |
|
| RestClient.rb:15:9:15:47 | call to delete | RestClient.rb:16:1:16:10 | call to body |
|
||||||
| RestClient.rb:18:9:18:45 | call to head | RestClient.rb:19:1:19:10 | call to body |
|
| RestClient.rb:18:9:18:45 | call to head | RestClient.rb:19:1:19:10 | call to body |
|
||||||
| RestClient.rb:21:9:21:48 | call to options | RestClient.rb:22:1:22:10 | call to body |
|
| RestClient.rb:21:9:21:48 | call to options | RestClient.rb:22:1:22:10 | call to body |
|
||||||
|
| RestClient.rb:25:9:25:21 | call to get | RestClient.rb:26:1:26:10 | call to body |
|
||||||
|
|||||||
@@ -19,4 +19,8 @@ resp6 = RestClient.head("http://example.com")
|
|||||||
resp6.body
|
resp6.body
|
||||||
|
|
||||||
resp7 = RestClient.options("http://example.com")
|
resp7 = RestClient.options("http://example.com")
|
||||||
resp7.body
|
resp7.body
|
||||||
|
|
||||||
|
resource8 = RestClient::Resource.new "http://example.com"
|
||||||
|
resp8 = resource8.get
|
||||||
|
resp8.body
|
||||||
49
ql/test/query-tests/security/cwe-295/Excon.rb
Normal file
49
ql/test/query-tests/security/cwe-295/Excon.rb
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
require "excon"
|
||||||
|
|
||||||
|
def method1
|
||||||
|
# BAD
|
||||||
|
Excon.defaults[:ssl_verify_peer] = false
|
||||||
|
Excon.get("http://example.com/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def method2
|
||||||
|
# BAD
|
||||||
|
Excon.ssl_verify_peer = false
|
||||||
|
Excon.get("http://example.com/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def method3(secure)
|
||||||
|
# BAD
|
||||||
|
Excon.defaults[:ssl_verify_peer] = (secure ? true : false)
|
||||||
|
Excon.get("http://example.com/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def method4
|
||||||
|
# BAD
|
||||||
|
conn = Excon::Connection.new("http://example.com/", ssl_verify_peer: false)
|
||||||
|
conn.get
|
||||||
|
end
|
||||||
|
|
||||||
|
def method5
|
||||||
|
# BAD
|
||||||
|
Excon.ssl_verify_peer = true
|
||||||
|
Excon.new("http://example.com/", ssl_verify_peer: false).get
|
||||||
|
end
|
||||||
|
|
||||||
|
def method6
|
||||||
|
# GOOD
|
||||||
|
Excon.defaults[:ssl_verify_peer] = true
|
||||||
|
Excon.get("http://example.com/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def method7
|
||||||
|
# GOOD
|
||||||
|
Excon.ssl_verify_peer = true
|
||||||
|
Excon.get("http://example.com/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def method8
|
||||||
|
# GOOD
|
||||||
|
Excon.defaults[:ssl_verify_peer] = false
|
||||||
|
Excon.new("http://example.com/", ssl_verify_peer: true)
|
||||||
|
end
|
||||||
28
ql/test/query-tests/security/cwe-295/Faraday.rb
Normal file
28
ql/test/query-tests/security/cwe-295/Faraday.rb
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
require "faraday"
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
connection = Faraday.new("http://example.com", ssl: { verify: false })
|
||||||
|
response = connection.get("/")
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
connection = Faraday.new("http://example.com", ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE })
|
||||||
|
response = connection.get("/")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
connection = Faraday.new("http://example.com")
|
||||||
|
response = connection.get("/")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
response = Faraday.get("http://example.com")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
connection = Faraday.new("http://example.com", ssl: { version: :TLSv1 })
|
||||||
|
response = connection.get("/")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
connection = Faraday.new("http://example.com", ssl: { verify: true })
|
||||||
|
response = connection.get("/")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
connection = Faraday.new("http://example.com", ssl: { verify_mode: OpenSSL::SSL::VERIFY_PEER })
|
||||||
|
response = connection.get("/")
|
||||||
18
ql/test/query-tests/security/cwe-295/HttpClient.rb
Normal file
18
ql/test/query-tests/security/cwe-295/HttpClient.rb
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
require "httpclient"
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
client = HTTPClient.new
|
||||||
|
client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
client.get("https://example.com")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
client = HTTPClient.new
|
||||||
|
client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||||
|
client.get("https://example.com")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
client = HTTPClient.new
|
||||||
|
client.get("https://example.com")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTPClient.get("https://example.com/")
|
||||||
37
ql/test/query-tests/security/cwe-295/Httparty.rb
Normal file
37
ql/test/query-tests/security/cwe-295/Httparty.rb
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
require "httparty"
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
HTTParty.get("http://example.com/", verify: false)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
HTTParty.get("http://example.com/", verify_peer: false)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
HTTParty.get("http://example.com/", { verify_peer: false })
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
HTTParty.post("http://example.com/", body: "some_data", verify: false)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
HTTParty.post("http://example.com/", { body: "some_data", verify: false })
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTParty.get("http://example.com/")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTParty.get("http://example.com/", verify: true)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTParty.get("http://example.com/", verify_peer: true)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTParty.post("http://example.com/", body: "some_data")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTParty.post("http://example.com/", body: "some_data", verify: true)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTParty.post("http://example.com/", { body: "some_data" })
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
HTTParty.post("http://example.com/", { body: "some_data", verify: true })
|
||||||
10
ql/test/query-tests/security/cwe-295/NetHttp.rb
Normal file
10
ql/test/query-tests/security/cwe-295/NetHttp.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
require "net/https"
|
||||||
|
require "uri"
|
||||||
|
|
||||||
|
uri = URI.parse "https://example.com/"
|
||||||
|
http = Net::HTTP.new uri.host, uri.port
|
||||||
|
http.use_ssl = true
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
|
request = Net::HTTP::Get.new uri.request_uri
|
||||||
|
response = http.request request
|
||||||
|
puts response.body
|
||||||
47
ql/test/query-tests/security/cwe-295/OpenURI.rb
Normal file
47
ql/test/query-tests/security/cwe-295/OpenURI.rb
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
require "open-uri"
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
Kernel.open("https://example.com", ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
Kernel.open("https://example.com", { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE })
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
options = { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }
|
||||||
|
Kernel.open("https://example.com", options)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
URI.parse("https://example.com").open(ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
URI.parse("https://example.com").open({ ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE })
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
options = { ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE }
|
||||||
|
URI.parse("https://example.com").open(options)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
Kernel.open("https://example.com")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
Kernel.open("https://example.com", ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
Kernel.open("https://example.com", { ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER })
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
options = { ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER }
|
||||||
|
Kernel.open("https://example.com", options)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
URI.parse("https://example.com").open
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
URI.parse("https://example.com").open(ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
URI.parse("https://example.com").open({ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER })
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
options = { ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER }
|
||||||
|
URI.parse("https://example.com").open(options)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
| Excon.rb:6:3:6:34 | call to get | This request $@. | Excon.rb:5:3:5:34 | call to []= | does not validate certificates |
|
||||||
|
| Excon.rb:12:3:12:34 | call to get | This request $@. | Excon.rb:11:3:11:23 | call to ssl_verify_peer= | does not validate certificates |
|
||||||
|
| Excon.rb:18:3:18:34 | call to get | This request $@. | Excon.rb:17:3:17:34 | call to []= | does not validate certificates |
|
||||||
|
| Excon.rb:24:3:24:10 | call to get | This request $@. | Excon.rb:23:55:23:76 | Pair | does not validate certificates |
|
||||||
|
| Excon.rb:30:3:30:62 | call to get | This request $@. | Excon.rb:30:36:30:57 | Pair | does not validate certificates |
|
||||||
|
| Faraday.rb:5:12:5:30 | call to get | This request $@. | Faraday.rb:4:48:4:69 | Pair | does not validate certificates |
|
||||||
|
| Faraday.rb:9:12:9:30 | call to get | This request $@. | Faraday.rb:8:48:8:94 | Pair | does not validate certificates |
|
||||||
|
| HttpClient.rb:6:1:6:33 | call to get | This request $@. | HttpClient.rb:5:1:5:29 | call to verify_mode= | does not validate certificates |
|
||||||
|
| Httparty.rb:4:1:4:50 | call to get | This request $@. | Httparty.rb:4:37:4:49 | Pair | does not validate certificates |
|
||||||
|
| Httparty.rb:7:1:7:55 | call to get | This request $@. | Httparty.rb:7:37:7:54 | Pair | does not validate certificates |
|
||||||
|
| Httparty.rb:10:1:10:59 | call to get | This request $@. | Httparty.rb:10:39:10:56 | Pair | does not validate certificates |
|
||||||
|
| Httparty.rb:13:1:13:70 | call to post | This request $@. | Httparty.rb:13:57:13:69 | Pair | does not validate certificates |
|
||||||
|
| Httparty.rb:16:1:16:74 | call to post | This request $@. | Httparty.rb:16:59:16:71 | Pair | does not validate certificates |
|
||||||
|
| NetHttp.rb:9:12:9:31 | call to request | This request $@. | NetHttp.rb:7:1:7:16 | ... = ... | does not validate certificates |
|
||||||
|
| OpenURI.rb:4:1:4:78 | call to open | This request $@. | OpenURI.rb:4:36:4:77 | Pair | does not validate certificates |
|
||||||
|
| OpenURI.rb:7:1:7:82 | call to open | This request $@. | OpenURI.rb:7:38:7:79 | Pair | does not validate certificates |
|
||||||
|
| OpenURI.rb:11:1:11:43 | call to open | This request $@. | OpenURI.rb:10:13:10:54 | Pair | does not validate certificates |
|
||||||
|
| OpenURI.rb:14:1:14:81 | call to open | This request $@. | OpenURI.rb:14:39:14:80 | Pair | does not validate certificates |
|
||||||
|
| OpenURI.rb:17:1:17:85 | call to open | This request $@. | OpenURI.rb:17:41:17:82 | Pair | does not validate certificates |
|
||||||
|
| OpenURI.rb:21:1:21:46 | call to open | This request $@. | OpenURI.rb:20:13:20:54 | Pair | does not validate certificates |
|
||||||
|
| RestClient.rb:5:12:5:23 | call to get | This request $@. | RestClient.rb:4:60:4:96 | Pair | does not validate certificates |
|
||||||
|
| RestClient.rb:9:12:9:23 | call to get | This request $@. | RestClient.rb:8:62:8:98 | Pair | does not validate certificates |
|
||||||
|
| RestClient.rb:14:12:14:23 | call to get | This request $@. | RestClient.rb:12:13:12:49 | Pair | does not validate certificates |
|
||||||
|
| Typhoeus.rb:4:1:4:62 | call to get | This request $@. | Typhoeus.rb:4:41:4:61 | Pair | does not validate certificates |
|
||||||
|
| Typhoeus.rb:8:1:8:54 | call to post | This request $@. | Typhoeus.rb:7:37:7:57 | Pair | does not validate certificates |
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
queries/security/cwe-295/RequestWithoutValidation.ql
|
||||||
33
ql/test/query-tests/security/cwe-295/RestClient.rb
Normal file
33
ql/test/query-tests/security/cwe-295/RestClient.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
require "rest-client"
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
resource = RestClient::Resource.new("https://example.com", verify_ssl: OpenSSL::SSL::VERIFY_NONE)
|
||||||
|
response = resource.get
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
resource = RestClient::Resource.new("https://example.com", { verify_ssl: OpenSSL::SSL::VERIFY_NONE })
|
||||||
|
response = resource.get
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
options = { verify_ssl: OpenSSL::SSL::VERIFY_NONE }
|
||||||
|
resource = RestClient::Resource.new("https://example.com", options)
|
||||||
|
response = resource.get
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
RestClient.get("https://example.com")
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
resource = RestClient::Resource.new("https://example.com")
|
||||||
|
response = resource.get
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
resource = RestClient::Resource.new("https://example.com", verify_ssl: OpenSSL::SSL::VERIFY_PEER)
|
||||||
|
response = resource.get
|
||||||
|
# BAD
|
||||||
|
resource = RestClient::Resource.new("https://example.com", { verify_ssl: OpenSSL::SSL::VERIFY_PEER })
|
||||||
|
response = resource.get
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
options = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
|
||||||
|
resource = RestClient::Resource.new("https://example.com", options)
|
||||||
|
response = resource.get
|
||||||
11
ql/test/query-tests/security/cwe-295/Typhoeus.rb
Normal file
11
ql/test/query-tests/security/cwe-295/Typhoeus.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
require "typhoeus"
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
Typhoeus.get("https://www.example.com", ssl_verifypeer: false)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
post_options = { body: "some data", ssl_verifypeer: false }
|
||||||
|
Typhoeus.post("https://www.example.com", post_options)
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
Typhoeus.get("https://www.example.com")
|
||||||
Reference in New Issue
Block a user