Ruby: Implement new disablesCertificateValidation for all HTTP client models

Sadly most alert text changed, but the two important changes are:

1. The request on RestClient.rb:19 now has an expanded alert text,
   highlighting where the origin of the value that disables certificate
   validation comes from. (in this case, it's trivial since it's the
   line right above)
2. We handle passing `false`/`OpenSSL::SSL::VERIFY_NONE` the same in the
   argument passing examples in Faraday.rb
This commit is contained in:
Rasmus Wriedt Larsen
2022-08-18 16:14:46 +02:00
parent 1f028ac206
commit 0ac3624342
9 changed files with 301 additions and 353 deletions

View File

@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
/**
* A call that makes an HTTP request using `Excon`.
@@ -61,27 +62,27 @@ class ExconHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode
result = connectionUse.(DataFlow::CallNode).getArgument(0)
}
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.getAValueReachableFromSource().(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+() = this.asExpr() and
disablingNode = disableCall and
not exists(DataFlow::Node arg, int i |
i > 0 and
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
exists(DataFlow::CallNode newCall | newCall = connectionNode.getAValueReachableFromSource() |
// Check for `ssl_verify_peer: false`
result = newCall.getKeywordArgument("ssl_verify_peer")
or
// using a hashliteral
exists(
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p,
DataFlow::Node key
|
argSetsVerifyPeer(arg, true, _)
// can't flow to argument 0, since that's the URL
optionsNode.flowsTo(newCall.getArgument(any(int i | i > 0))) and
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
key.asExpr() = p.getKey() and
key.getALocalSource()
.asExpr()
.getExpr()
.getConstantValue()
.isStringlikeValue("ssl_verify_peer") and
result.asExpr() = p.getValue()
)
)
}
@@ -89,68 +90,59 @@ class ExconHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(ExconDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue()
or
// We set `Excon.defaults[:ssl_verify_peer]` or `Excon.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, BooleanLiteral value |
// Excon.defaults[:ssl_verify_peer]
disableCall = API::getTopLevelMember("Excon").getReturn("defaults").getAMethodCall("[]=") and
disableCall
.getArgument(0)
.getALocalSource()
.asExpr()
.getExpr()
.getConstantValue()
.isStringlikeValue("ssl_verify_peer") and
disablingNode = disableCall.getArgument(1) and
argumentOrigin = disablingNode.getALocalSource() and
value = argumentOrigin.asExpr().getExpr()
or
// Excon.ssl_verify_peer
disableCall = API::getTopLevelMember("Excon").getAMethodCall("ssl_verify_peer=") and
disablingNode = disableCall.getArgument(0) and
argumentOrigin = disablingNode.getALocalSource() and
value = argumentOrigin.asExpr().getExpr()
|
value.getValue() = false and
disableCall.asExpr().getASuccessor+() = this.asExpr() and
// no `ssl_verify_peer: true` in the request call.
not this.getCertificateValidationControllingValue()
.getALocalSource()
.asExpr()
.getExpr()
.(BooleanLiteral)
.getValue() = true
)
}
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(), value) and
kvNode = arg
or
// Or as a single hash argument, e.g.:
// Excon.get(..., { ssl_verify_peer: false, ... })
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
isSslVerifyPeerPair(p, value) and
optionsNode.flowsTo(arg) and
kvNode.asExpr() = p
)
}
/** A configuration to track values that can disable certificate validation for Excon. */
private class ExconDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
ExconDisablesCertificateValidationConfiguration() {
this = "ExconDisablesCertificateValidationConfiguration"
}
/**
* 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)
}
override predicate isSource(DataFlow::Node source) {
source.asExpr().getExpr().(BooleanLiteral).isFalse()
}
private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
exists(DataFlow::LocalSourceNode literal |
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("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(CfgNodes::ExprNodes::PairCfgNode p, boolean value) {
exists(DataFlow::Node key, DataFlow::Node valueNode |
key.asExpr() = p.getKey() and
valueNode.asExpr() = p.getValue() and
isSslVerifyPeerLiteral(key) and
hasBooleanValue(valueNode, value)
)
override predicate isSink(DataFlow::Node sink) {
sink = any(ExconHttpRequest req).getCertificateValidationControllingValue()
}
}

View File

@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
/**
* A call that makes an HTTP request using `Faraday`.
@@ -49,28 +50,36 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNod
result = connectionUse.(DataFlow::CallNode).getKeywordArgument("url")
}
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
/** Gets the value that controls certificate validation, if any, with argument name `name`. */
DataFlow::Node getCertificateValidationControllingValue(string argName) {
// `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.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
// Either passed as an individual key:value argument, e.g.:
// Faraday.new(..., ssl: {...})
isSslOptionsPairDisablingValidation(arg.asExpr()) and
disablingNode = arg
argName in ["verify", "verify_mode"] and
exists(DataFlow::Node sslValue, DataFlow::CallNode newCall |
newCall = connectionNode.getAValueReachableFromSource() and
sslValue = newCall.getKeywordArgument("ssl")
or
// Or as a single hash argument, e.g.:
// Faraday.new(..., { ssl: {...} })
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
// using a hashliteral
exists(
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p,
DataFlow::Node key
|
// can't flow to argument 0, since that's the URL
optionsNode.flowsTo(newCall.getArgument(any(int i | i > 0))) and
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
isSslOptionsPairDisablingValidation(p) and
optionsNode.flowsTo(arg) and
disablingNode.asExpr() = p
key.asExpr() = p.getKey() and
key.getALocalSource().asExpr().getExpr().getConstantValue().isStringlikeValue("ssl") and
sslValue.asExpr() = p.getValue()
)
|
exists(CfgNodes::ExprNodes::PairCfgNode p, DataFlow::Node key |
p = sslValue.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
key.asExpr() = p.getKey() and
key.getALocalSource().asExpr().getExpr().getConstantValue().isStringlikeValue(argName) and
result.asExpr() = p.getValue()
)
)
}
@@ -78,90 +87,29 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNod
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(FaradayDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue(_)
}
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(CfgNodes::ExprNodes::PairCfgNode p) {
exists(DataFlow::Node key, DataFlow::Node value |
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSymbolLiteral(key, "ssl") and
(isHashWithVerifyFalse(value) or isHashWithVerifyModeNone(value))
)
}
/** A configuration to track values that can disable certificate validation for Faraday. */
private class FaradayDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
FaradayDisablesCertificateValidationConfiguration() {
this = "FaradayDisablesCertificateValidationConfiguration"
}
/** Holds if `node` represents the symbol literal with the given `valueText`. */
private predicate isSymbolLiteral(DataFlow::Node node, string valueText) {
exists(DataFlow::LocalSourceNode literal |
literal.asExpr().getExpr().getConstantValue().isStringlikeValue(valueText) and
literal.flowsTo(node)
)
}
override predicate isSource(DataFlow::Node source, DataFlowImplForLibraries::FlowState state) {
source.asExpr().getExpr().(BooleanLiteral).isFalse() and
state = "verify"
or
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource() and
state = "verify_mode"
}
/**
* 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().(CfgNodes::ExprNodes::HashLiteralCfgNode).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().(CfgNodes::ExprNodes::HashLiteralCfgNode).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(CfgNodes::ExprNodes::PairCfgNode p) {
exists(DataFlow::Node key, DataFlow::Node value |
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSymbolLiteral(key, "verify_mode") and
value =
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource()
)
}
/**
* Holds if the pair `p` has the key `:verify` and the value `false`.
*/
private predicate isVerifyFalsePair(CfgNodes::ExprNodes::PairCfgNode p) {
exists(DataFlow::Node key, DataFlow::Node value |
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSymbolLiteral(key, "verify") and
isFalse(value)
)
}
/** Holds if `node` can contain the Boolean value `false`. */
private predicate isFalse(DataFlow::Node node) {
exists(DataFlow::LocalSourceNode literal |
literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
literal.flowsTo(node)
)
override predicate isSink(DataFlow::Node sink, DataFlowImplForLibraries::FlowState state) {
sink = any(FaradayHttpRequest req).getCertificateValidationControllingValue(state)
}
}

View File

@@ -6,6 +6,7 @@ private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
/**
* A call that makes an HTTP request using `HTTPClient`.
@@ -46,24 +47,42 @@ class HttpClientRequest extends HTTP::Client::Request::Range, DataFlow::CallNode
result = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])
}
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
// 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=").asSource() and
disablingNode.(DataFlow::CallNode).getArgument(0) =
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource()
result =
connectionNode
.getReturn("ssl_config")
.getReturn("verify_mode=")
.asSource()
.(DataFlow::CallNode)
.getArgument(0)
}
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(HttpClientDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue()
}
override string getFramework() { result = "HTTPClient" }
}
/** A configuration to track values that can disable certificate validation for HttpClient. */
private class HttpClientDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
HttpClientDisablesCertificateValidationConfiguration() {
this = "HttpClientDisablesCertificateValidationConfiguration"
}
override predicate isSource(DataFlow::Node source) {
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(HttpClientRequest req).getCertificateValidationControllingValue()
}
}

View File

@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
/**
* A call that makes an HTTP request using `HTTParty`.
@@ -47,64 +48,49 @@ class HttpartyRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
result = this
}
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() = this.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i)
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgument(["verify", "verify_peer"])
or
// using a hashliteral
exists(
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p, DataFlow::Node key
|
// Either passed as an individual key:value argument, e.g.:
// HTTParty.get(..., verify: false)
isVerifyFalsePair(arg.asExpr()) and
disablingNode = arg
or
// Or as a single hash argument, e.g.:
// HTTParty.get(..., { verify: false, ... })
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
isVerifyFalsePair(p) and
optionsNode.flowsTo(arg) and
disablingNode.asExpr() = p
)
// can't flow to argument 0, since that's the URL
optionsNode.flowsTo(this.getArgument(any(int i | i > 0))) and
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
key.asExpr() = p.getKey() and
key.getALocalSource()
.asExpr()
.getExpr()
.getConstantValue()
.isStringlikeValue(["verify", "verify_peer"]) and
result.asExpr() = p.getValue()
)
}
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(HttpartyDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue()
}
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().getConstantValue().isStringlikeValue(["verify", "verify_peer"]) and
literal.flowsTo(node)
)
}
/** A configuration to track values that can disable certificate validation for Httparty. */
private class HttpartyDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
HttpartyDisablesCertificateValidationConfiguration() {
this = "HttpartyDisablesCertificateValidationConfiguration"
}
/** Holds if `node` can contain the Boolean value `false`. */
private predicate isFalse(DataFlow::Node node) {
exists(DataFlow::LocalSourceNode literal |
literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
literal.flowsTo(node)
)
}
override predicate isSource(DataFlow::Node source) {
source.asExpr().getExpr().(BooleanLiteral).isFalse()
}
/**
* Holds if `p` is the pair `verify: false` or `verify_peer: false`.
*/
private predicate isVerifyFalsePair(CfgNodes::ExprNodes::PairCfgNode p) {
exists(DataFlow::Node key, DataFlow::Node value |
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isVerifyLiteral(key) and
isFalse(value)
)
override predicate isSink(DataFlow::Node sink) {
sink = any(HttpartyRequest req).getCertificateValidationControllingValue()
}
}

View File

@@ -8,6 +8,7 @@ private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.internal.DataFlowPublic
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
/**
* A `Net::HTTP` call which initiates an HTTP request.
@@ -65,20 +66,16 @@ class NetHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
override DataFlow::Node getResponseBody() { result = responseBody }
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
// 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")
.getAValueReachableFromSource() and
setter.asExpr().getExpr().(SetterMethodCall).getMethodName() = "verify_mode=" and
disablingNode = setter.getArgument(0) and
result = setter.getArgument(0) and
localFlow(setter.getReceiver(), request.getReceiver())
)
}
@@ -86,9 +83,25 @@ class NetHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(NetHttpDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue()
}
override string getFramework() { result = "Net::HTTP" }
}
/** A configuration to track values that can disable certificate validation for NetHttp. */
private class NetHttpDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
NetHttpDisablesCertificateValidationConfiguration() {
this = "NetHttpDisablesCertificateValidationConfiguration"
}
override predicate isSource(DataFlow::Node source) {
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(NetHttpRequest req).getCertificateValidationControllingValue()
}
}

View File

@@ -8,6 +8,7 @@ private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.frameworks.Core
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
/**
* A call that makes an HTTP request using `OpenURI` via `URI.open` or
@@ -36,18 +37,32 @@ class OpenUriRequest extends HTTP::Client::Request::Range, DataFlow::CallNode {
result = requestNode.getAMethodCall(["read", "readlines"])
}
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
exists(DataFlow::Node arg |
arg.asExpr() = this.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getAnArgument() and
argumentDisablesValidation(arg, disablingNode)
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgument("ssl_verify_mode")
or
// using a hashliteral
exists(
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p, DataFlow::Node key
|
optionsNode.flowsTo(this.getArgument(_)) and
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
key.asExpr() = p.getKey() and
key.getALocalSource()
.asExpr()
.getExpr()
.getConstantValue()
.isStringlikeValue("ssl_verify_mode") and
result.asExpr() = p.getValue()
)
}
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(OpenUriDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue()
}
override string getFramework() { result = "OpenURI" }
@@ -74,63 +89,51 @@ class OpenUriKernelOpenRequest extends HTTP::Client::Request::Range, DataFlow::C
this.(DataFlow::LocalSourceNode).flowsTo(result.getReceiver())
}
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
exists(DataFlow::Node arg, int i |
i > 0 and
arg.asExpr() = this.asExpr().(CfgNodes::ExprNodes::MethodCallCfgNode).getArgument(i) and
argumentDisablesValidation(arg, disablingNode)
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgument("ssl_verify_mode")
or
// using a hashliteral
exists(
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p, DataFlow::Node key
|
// can't flow to argument 0, since that's the URL
optionsNode.flowsTo(this.getArgument(any(int i | i > 0))) and
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
key.asExpr() = p.getKey() and
key.getALocalSource()
.asExpr()
.getExpr()
.getConstantValue()
.isStringlikeValue("ssl_verify_mode") and
result.asExpr() = p.getValue()
)
}
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(OpenUriDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue()
}
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()) 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, CfgNodes::ExprNodes::PairCfgNode p |
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
isSslVerifyModeNonePair(p) and
optionsNode.flowsTo(arg) and
disablingNode.asExpr() = p
)
}
/** A configuration to track values that can disable certificate validation for OpenURI. */
private class OpenUriDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
OpenUriDisablesCertificateValidationConfiguration() {
this = "OpenUriDisablesCertificateValidationConfiguration"
}
/** Holds if `p` is the pair `ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE`. */
private predicate isSslVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
exists(DataFlow::Node key, DataFlow::Node value |
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSslVerifyModeLiteral(key) and
value =
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource()
)
}
override predicate isSource(DataFlow::Node source) {
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
}
/** Holds if `node` can represent the symbol literal `:ssl_verify_mode`. */
private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
exists(DataFlow::LocalSourceNode literal |
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("ssl_verify_mode") and
literal.flowsTo(node)
)
override predicate isSink(DataFlow::Node sink) {
sink = any(OpenUriRequest req).getCertificateValidationControllingValue()
or
sink = any(OpenUriKernelOpenRequest req).getCertificateValidationControllingValue()
}
}

View File

@@ -49,8 +49,6 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range, DataFlow::Call
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
// `RestClient::Resource::new` takes an options hash argument, and we're
// looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`.
exists(DataFlow::CallNode newCall | newCall = connectionNode.getAValueReachableFromSource() |
result = newCall.getKeywordArgument("verify_ssl")
or

View File

@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
/**
* A call that makes an HTTP request using `Typhoeus`.
@@ -28,59 +29,46 @@ class TyphoeusHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNo
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() = this.asExpr().getExpr().(MethodCall).getArgument(i)
/** Gets the value that controls certificate validation, if any. */
DataFlow::Node getCertificateValidationControllingValue() {
result = this.getKeywordArgument("ssl_verifypeer")
or
// using a hashliteral
exists(
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p,
DataFlow::Node key
|
// Either passed as an individual key:value argument, e.g.:
// Typhoeus.get(..., ssl_verifypeer: false)
isSslVerifyPeerFalsePair(arg.asExpr()) and
disablingNode = arg
or
// Or as a single hash argument, e.g.:
// Typhoeus.get(..., { ssl_verifypeer: false, ... })
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
isSslVerifyPeerFalsePair(p) and
optionsNode.flowsTo(arg) and
disablingNode.asExpr() = p
)
// can't flow to argument 0, since that's the URL
optionsNode.flowsTo(this.getArgument(any(int i | i > 0))) and
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
key.asExpr() = p.getKey() and
key.getALocalSource().asExpr().getExpr().getConstantValue().isStringlikeValue("ssl_verifypeer") and
result.asExpr() = p.getValue()
)
}
override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
disablesCertificateValidation(disablingNode) and
argumentOrigin = disablingNode
any(TyphoeusDisablesCertificateValidationConfiguration config)
.hasFlow(argumentOrigin, disablingNode) and
disablingNode = this.getCertificateValidationControllingValue()
}
override string getFramework() { result = "Typhoeus" }
}
/** Holds if `p` is the pair `ssl_verifypeer: false`. */
private predicate isSslVerifyPeerFalsePair(CfgNodes::ExprNodes::PairCfgNode p) {
exists(DataFlow::Node key, DataFlow::Node value |
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSslVerifyPeerLiteral(key) and
isFalse(value)
)
}
/** A configuration to track values that can disable certificate validation for Typhoeus. */
private class TyphoeusDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
TyphoeusDisablesCertificateValidationConfiguration() {
this = "TyphoeusDisablesCertificateValidationConfiguration"
}
/** Holds if `node` represents the symbol literal `verify` or `verify_peer`. */
private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
exists(DataFlow::LocalSourceNode literal |
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("ssl_verifypeer") and
literal.flowsTo(node)
)
}
override predicate isSource(DataFlow::Node source) {
source.asExpr().getExpr().(BooleanLiteral).isFalse()
}
/** Holds if `node` can contain the Boolean value `false`. */
private predicate isFalse(DataFlow::Node node) {
exists(DataFlow::LocalSourceNode literal |
literal.asExpr().getExpr().(BooleanLiteral).isFalse() and
literal.flowsTo(node)
)
override predicate isSink(DataFlow::Node sink) {
sink = any(TyphoeusHttpRequest req).getCertificateValidationControllingValue()
}
}

View File

@@ -1,27 +1,28 @@
| Excon.rb:6:3:6:34 | call to get | This request may run without certificate validation because it is $@. | Excon.rb:5:3:5:34 | call to []= | disabled here | Excon.rb:5:3:5:34 | call to []= | here |
| Excon.rb:12:3:12:34 | call to get | This request may run without certificate validation because it is $@. | Excon.rb:11:3:11:23 | call to ssl_verify_peer= | disabled here | Excon.rb:11:3:11:23 | call to ssl_verify_peer= | here |
| Excon.rb:18:3:18:34 | call to get | This request may run without certificate validation because it is $@. | Excon.rb:17:3:17:34 | call to []= | disabled here | Excon.rb:17:3:17:34 | call to []= | here |
| Excon.rb:24:3:24:10 | call to get | This request may run without certificate validation because it is $@. | Excon.rb:23:55:23:76 | Pair | disabled here | Excon.rb:23:55:23:76 | Pair | here |
| Excon.rb:30:3:30:62 | call to get | This request may run without certificate validation because it is $@. | Excon.rb:30:36:30:57 | Pair | disabled here | Excon.rb:30:36:30:57 | Pair | here |
| Faraday.rb:5:12:5:30 | call to get | This request may run without certificate validation because it is $@. | Faraday.rb:4:48:4:69 | Pair | disabled here | Faraday.rb:4:48:4:69 | Pair | here |
| Faraday.rb:9:12:9:30 | call to get | This request may run without certificate validation because it is $@. | Faraday.rb:8:48:8:94 | Pair | disabled here | Faraday.rb:8:48:8:94 | Pair | here |
| Faraday.rb:44:16:44:35 | call to get | This request may run without certificate validation because it is $@. | Faraday.rb:43:36:43:60 | Pair | disabled here | Faraday.rb:43:36:43:60 | Pair | here |
| HttpClient.rb:6:1:6:33 | call to get | This request may run without certificate validation because it is $@. | HttpClient.rb:5:1:5:29 | call to verify_mode= | disabled here | HttpClient.rb:5:1:5:29 | call to verify_mode= | here |
| Httparty.rb:4:1:4:50 | call to get | This request may run without certificate validation because it is $@. | Httparty.rb:4:37:4:49 | Pair | disabled here | Httparty.rb:4:37:4:49 | Pair | here |
| Httparty.rb:7:1:7:55 | call to get | This request may run without certificate validation because it is $@. | Httparty.rb:7:37:7:54 | Pair | disabled here | Httparty.rb:7:37:7:54 | Pair | here |
| Httparty.rb:10:1:10:59 | call to get | This request may run without certificate validation because it is $@. | Httparty.rb:10:39:10:56 | Pair | disabled here | Httparty.rb:10:39:10:56 | Pair | here |
| Httparty.rb:13:1:13:70 | call to post | This request may run without certificate validation because it is $@. | Httparty.rb:13:57:13:69 | Pair | disabled here | Httparty.rb:13:57:13:69 | Pair | here |
| Httparty.rb:16:1:16:74 | call to post | This request may run without certificate validation because it is $@. | Httparty.rb:16:59:16:71 | Pair | disabled here | Httparty.rb:16:59:16:71 | Pair | here |
| NetHttp.rb:9:12:9:31 | call to request | This request may run without certificate validation because it is $@. | NetHttp.rb:7:20:7:44 | ... = ... | disabled here | NetHttp.rb:7:20:7:44 | ... = ... | here |
| OpenURI.rb:4:1:4:78 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:4:36:4:77 | Pair | disabled here | OpenURI.rb:4:36:4:77 | Pair | here |
| OpenURI.rb:7:1:7:82 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:7:38:7:79 | Pair | disabled here | OpenURI.rb:7:38:7:79 | Pair | here |
| OpenURI.rb:11:1:11:43 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:10:13:10:54 | Pair | disabled here | OpenURI.rb:10:13:10:54 | Pair | here |
| OpenURI.rb:14:1:14:81 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:14:39:14:80 | Pair | disabled here | OpenURI.rb:14:39:14:80 | Pair | here |
| OpenURI.rb:17:1:17:85 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:17:41:17:82 | Pair | disabled here | OpenURI.rb:17:41:17:82 | Pair | here |
| OpenURI.rb:21:1:21:46 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:20:13:20:54 | Pair | disabled here | OpenURI.rb:20:13:20:54 | Pair | here |
| Excon.rb:6:3:6:34 | call to get | This request may run without certificate validation because it is $@ by the value from $@. | Excon.rb:5:38:5:42 | ... = ... | disabled here | Excon.rb:5:38:5:42 | false | here |
| Excon.rb:12:3:12:34 | call to get | This request may run without certificate validation because it is $@ by the value from $@. | Excon.rb:11:27:11:31 | ... = ... | disabled here | Excon.rb:11:27:11:31 | false | here |
| Excon.rb:18:3:18:34 | call to get | This request may run without certificate validation because it is $@ by the value from $@. | Excon.rb:17:38:17:60 | ... = ... | disabled here | Excon.rb:17:55:17:59 | false | here |
| Excon.rb:24:3:24:10 | call to get | This request may run without certificate validation because it is $@. | Excon.rb:23:72:23:76 | false | disabled here | Excon.rb:23:72:23:76 | false | here |
| Excon.rb:30:3:30:62 | call to get | This request may run without certificate validation because it is $@. | Excon.rb:30:53:30:57 | false | disabled here | Excon.rb:30:53:30:57 | false | here |
| Faraday.rb:5:12:5:30 | call to get | This request may run without certificate validation because it is $@. | Faraday.rb:4:63:4:67 | false | disabled here | Faraday.rb:4:63:4:67 | false | here |
| Faraday.rb:9:12:9:30 | call to get | This request may run without certificate validation because it is $@. | Faraday.rb:8:68:8:92 | VERIFY_NONE | disabled here | Faraday.rb:8:68:8:92 | VERIFY_NONE | here |
| Faraday.rb:35:16:35:35 | call to get | This request may run without certificate validation because it is $@ by the value from $@. | Faraday.rb:34:51:34:53 | arg | disabled here | Faraday.rb:38:42:38:46 | false | here |
| Faraday.rb:44:16:44:35 | call to get | This request may run without certificate validation because it is $@ by the value from $@. | Faraday.rb:43:56:43:58 | arg | disabled here | Faraday.rb:47:47:47:71 | VERIFY_NONE | here |
| HttpClient.rb:6:1:6:33 | call to get | This request may run without certificate validation because it is $@ by the value from $@. | HttpClient.rb:5:33:5:57 | ... = ... | disabled here | HttpClient.rb:5:33:5:57 | VERIFY_NONE | here |
| Httparty.rb:4:1:4:50 | call to get | This request may run without certificate validation because it is $@. | Httparty.rb:4:45:4:49 | false | disabled here | Httparty.rb:4:45:4:49 | false | here |
| Httparty.rb:7:1:7:55 | call to get | This request may run without certificate validation because it is $@. | Httparty.rb:7:50:7:54 | false | disabled here | Httparty.rb:7:50:7:54 | false | here |
| Httparty.rb:10:1:10:59 | call to get | This request may run without certificate validation because it is $@. | Httparty.rb:10:52:10:56 | false | disabled here | Httparty.rb:10:52:10:56 | false | here |
| Httparty.rb:13:1:13:70 | call to post | This request may run without certificate validation because it is $@. | Httparty.rb:13:65:13:69 | false | disabled here | Httparty.rb:13:65:13:69 | false | here |
| Httparty.rb:16:1:16:74 | call to post | This request may run without certificate validation because it is $@. | Httparty.rb:16:67:16:71 | false | disabled here | Httparty.rb:16:67:16:71 | false | here |
| NetHttp.rb:9:12:9:31 | call to request | This request may run without certificate validation because it is $@ by the value from $@. | NetHttp.rb:7:20:7:44 | ... = ... | disabled here | NetHttp.rb:7:20:7:44 | VERIFY_NONE | here |
| OpenURI.rb:4:1:4:78 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:4:53:4:77 | VERIFY_NONE | disabled here | OpenURI.rb:4:53:4:77 | VERIFY_NONE | here |
| OpenURI.rb:7:1:7:82 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:7:55:7:79 | VERIFY_NONE | disabled here | OpenURI.rb:7:55:7:79 | VERIFY_NONE | here |
| OpenURI.rb:11:1:11:43 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:10:30:10:54 | VERIFY_NONE | disabled here | OpenURI.rb:10:30:10:54 | VERIFY_NONE | here |
| OpenURI.rb:14:1:14:81 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:14:56:14:80 | VERIFY_NONE | disabled here | OpenURI.rb:14:56:14:80 | VERIFY_NONE | here |
| OpenURI.rb:17:1:17:85 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:17:58:17:82 | VERIFY_NONE | disabled here | OpenURI.rb:17:58:17:82 | VERIFY_NONE | here |
| OpenURI.rb:21:1:21:46 | call to open | This request may run without certificate validation because it is $@. | OpenURI.rb:20:30:20:54 | VERIFY_NONE | disabled here | OpenURI.rb:20:30:20:54 | VERIFY_NONE | here |
| RestClient.rb:5:12:5:23 | call to get | This request may run without certificate validation because it is $@. | RestClient.rb:4:72:4:96 | VERIFY_NONE | disabled here | RestClient.rb:4:72:4:96 | VERIFY_NONE | here |
| RestClient.rb:9:12:9:23 | call to get | This request may run without certificate validation because it is $@. | RestClient.rb:8:74:8:98 | VERIFY_NONE | disabled here | RestClient.rb:8:74:8:98 | VERIFY_NONE | here |
| RestClient.rb:14:12:14:23 | call to get | This request may run without certificate validation because it is $@. | RestClient.rb:12:25:12:49 | VERIFY_NONE | disabled here | RestClient.rb:12:25:12:49 | VERIFY_NONE | here |
| RestClient.rb:19:12:19:23 | call to get | This request may run without certificate validation because it is $@ by the value from $@. | RestClient.rb:18:72:18:76 | value | disabled here | RestClient.rb:17:9:17:33 | VERIFY_NONE | here |
| Typhoeus.rb:4:1:4:62 | call to get | This request may run without certificate validation because it is $@. | Typhoeus.rb:4:41:4:61 | Pair | disabled here | Typhoeus.rb:4:41:4:61 | Pair | here |
| Typhoeus.rb:8:1:8:54 | call to post | This request may run without certificate validation because it is $@. | Typhoeus.rb:7:37:7:57 | Pair | disabled here | Typhoeus.rb:7:37:7:57 | Pair | here |
| Typhoeus.rb:4:1:4:62 | call to get | This request may run without certificate validation because it is $@. | Typhoeus.rb:4:57:4:61 | false | disabled here | Typhoeus.rb:4:57:4:61 | false | here |
| Typhoeus.rb:8:1:8:54 | call to post | This request may run without certificate validation because it is $@. | Typhoeus.rb:7:53:7:57 | false | disabled here | Typhoeus.rb:7:53:7:57 | false | here |