Skip to content

Commit 1f028ac

Browse files
committed
Ruby: Implement new disablesCertificateValidation for RestClient
1 parent 07d9591 commit 1f028ac

File tree

2 files changed

+34
-40
lines changed

2 files changed

+34
-40
lines changed

ruby/ql/lib/codeql/ruby/frameworks/http_clients/RestClient.qll

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
77
private import codeql.ruby.Concepts
88
private import codeql.ruby.ApiGraphs
99
private import codeql.ruby.DataFlow
10+
private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
1011

1112
/**
1213
* A call that makes an HTTP request using `RestClient`.
@@ -46,57 +47,50 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range, DataFlow::Call
4647

4748
override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
4849

49-
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
50+
/** Gets the value that controls certificate validation, if any. */
51+
DataFlow::Node getCertificateValidationControllingValue() {
5052
// `RestClient::Resource::new` takes an options hash argument, and we're
5153
// looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`.
52-
exists(DataFlow::Node arg, int i |
53-
i > 0 and
54-
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
55-
|
56-
// Either passed as an individual key:value argument, e.g.:
57-
// RestClient::Resource.new(..., verify_ssl: OpenSSL::SSL::VERIFY_NONE)
58-
isVerifySslNonePair(arg.asExpr()) and
59-
disablingNode = arg
54+
exists(DataFlow::CallNode newCall | newCall = connectionNode.getAValueReachableFromSource() |
55+
result = newCall.getKeywordArgument("verify_ssl")
6056
or
61-
// Or as a single hash argument, e.g.:
62-
// RestClient::Resource.new(..., { verify_ssl: OpenSSL::SSL::VERIFY_NONE })
63-
exists(DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p |
57+
// using a hashliteral
58+
exists(
59+
DataFlow::LocalSourceNode optionsNode, CfgNodes::ExprNodes::PairCfgNode p,
60+
DataFlow::Node key
61+
|
62+
// can't flow to argument 0, since that's the URL
63+
optionsNode.flowsTo(newCall.getArgument(any(int i | i > 0))) and
6464
p = optionsNode.asExpr().(CfgNodes::ExprNodes::HashLiteralCfgNode).getAKeyValuePair() and
65-
isVerifySslNonePair(p) and
66-
optionsNode.flowsTo(arg) and
67-
disablingNode.asExpr() = p
65+
key.asExpr() = p.getKey() and
66+
key.getALocalSource().asExpr().getExpr().getConstantValue().isStringlikeValue("verify_ssl") and
67+
result.asExpr() = p.getValue()
6868
)
6969
)
7070
}
7171

7272
override predicate disablesCertificateValidation(
7373
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
7474
) {
75-
disablesCertificateValidation(disablingNode) and
76-
argumentOrigin = disablingNode
75+
any(RestClientDisablesCertificateValidationConfiguration config)
76+
.hasFlow(argumentOrigin, disablingNode) and
77+
disablingNode = this.getCertificateValidationControllingValue()
7778
}
7879

7980
override string getFramework() { result = "RestClient" }
8081
}
8182

82-
/** Holds if `p` is the pair `verify_ssl: OpenSSL::SSL::VERIFY_NONE`. */
83-
private predicate isVerifySslNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
84-
exists(DataFlow::Node key, DataFlow::Node value |
85-
key.asExpr() = p.getKey() and
86-
value.asExpr() = p.getValue() and
87-
isSslVerifyModeLiteral(key) and
88-
value =
89-
API::getTopLevelMember("OpenSSL")
90-
.getMember("SSL")
91-
.getMember("VERIFY_NONE")
92-
.getAValueReachableFromSource()
93-
)
94-
}
83+
/** A configuration to track values that can disable certificate validation for RestClient. */
84+
private class RestClientDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries::Configuration {
85+
RestClientDisablesCertificateValidationConfiguration() {
86+
this = "RestClientDisablesCertificateValidationConfiguration"
87+
}
9588

96-
/** Holds if `node` can represent the symbol literal `:verify_ssl`. */
97-
private predicate isSslVerifyModeLiteral(DataFlow::Node node) {
98-
exists(DataFlow::LocalSourceNode literal |
99-
literal.asExpr().getExpr().getConstantValue().isStringlikeValue("verify_ssl") and
100-
literal.flowsTo(node)
101-
)
89+
override predicate isSource(DataFlow::Node source) {
90+
source = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").asSource()
91+
}
92+
93+
override predicate isSink(DataFlow::Node sink) {
94+
sink = any(RestClientHttpRequest req).getCertificateValidationControllingValue()
95+
}
10296
}

ruby/ql/test/query-tests/security/cwe-295/RequestWithoutValidation.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
| 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 |
2020
| 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 |
2121
| 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 |
22-
| RestClient.rb:5:12:5:23 | call to get | This request may run without certificate validation because it is $@. | RestClient.rb:4:60:4:96 | Pair | disabled here | RestClient.rb:4:60:4:96 | Pair | here |
23-
| RestClient.rb:9:12:9:23 | call to get | This request may run without certificate validation because it is $@. | RestClient.rb:8:62:8:98 | Pair | disabled here | RestClient.rb:8:62:8:98 | Pair | here |
24-
| RestClient.rb:14:12:14:23 | call to get | This request may run without certificate validation because it is $@. | RestClient.rb:12:13:12:49 | Pair | disabled here | RestClient.rb:12:13:12:49 | Pair | here |
25-
| RestClient.rb:19:12:19:23 | call to get | This request may run without certificate validation because it is $@. | RestClient.rb:18:60:18:76 | Pair | disabled here | RestClient.rb:18:60:18:76 | Pair | here |
22+
| 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 |
23+
| 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 |
24+
| 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 |
25+
| 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 |
2626
| 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 |
2727
| 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 |

0 commit comments

Comments
 (0)