@@ -18,29 +18,113 @@ private import codeql.ruby.ApiGraphs
1818 * https://github.com/excon/excon/blob/master/README.md
1919 */
2020class ExconHttpRequest extends HTTP::Client::Request::Range {
21- DataFlow::Node request;
22- DataFlow::CallNode responseBody;
21+ DataFlow::Node requestUse;
22+ API::Node requestNode;
23+ API::Node connectionNode;
2324
2425 ExconHttpRequest() {
25- exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
26- requestNode =
27- [
28- // one-off requests
29- API::getTopLevelMember("Excon"),
30- // connection re-use
31- API::getTopLevelMember("Excon").getInstance()
32- ]
33- .getReturn([
34- // Excon#request exists but Excon.request doesn't.
35- // This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
36- "get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
37- ]) and
38- responseBody = requestNode.getAMethodCall("body") and
39- this = request.asExpr().getExpr()
40- )
26+ requestUse = requestNode.getAnImmediateUse() and
27+ connectionNode =
28+ [
29+ // one-off requests
30+ API::getTopLevelMember("Excon"),
31+ // connection re-use
32+ API::getTopLevelMember("Excon").getInstance(),
33+ API::getTopLevelMember("Excon").getMember("Connection").getInstance()
34+ ] and
35+ requestNode =
36+ connectionNode
37+ .getReturn([
38+ // Excon#request exists but Excon.request doesn't.
39+ // This shouldn't be a problem - in real code the latter would raise NoMethodError anyway.
40+ "get", "head", "delete", "options", "post", "put", "patch", "trace", "request"
41+ ]) and
42+ this = requestUse.asExpr().getExpr()
4143 }
4244
43- override DataFlow::Node getResponseBody() { result = responseBody }
45+ override DataFlow::Node getResponseBody() { result = requestNode.getAMethodCall("body") }
46+
47+ override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
48+ // Check for `ssl_verify_peer: false` in the options hash.
49+ exists(DataFlow::Node arg, int i |
50+ i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
51+ |
52+ argSetsVerifyPeer(arg, false, disablingNode)
53+ )
54+ or
55+ // Or we see a call to `Excon.defaults[:ssl_verify_peer] = false` before the
56+ // request, and no `ssl_verify_peer: true` in the explicit options hash for
57+ // the request call.
58+ exists(DataFlow::CallNode disableCall |
59+ setsDefaultVerification(disableCall, false) and
60+ disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and
61+ disablingNode = disableCall and
62+ not exists(DataFlow::Node arg, int i |
63+ i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
64+ |
65+ argSetsVerifyPeer(arg, true, _)
66+ )
67+ )
68+ }
4469
4570 override string getFramework() { result = "Excon" }
4671}
72+
73+ /**
74+ * Holds if `arg` represents an options hash that contains the key
75+ * `:ssl_verify_peer` with `value`, where `kvNode` is the data-flow node for
76+ * this key-value pair.
77+ */
78+ predicate argSetsVerifyPeer(DataFlow::Node arg, boolean value, DataFlow::Node kvNode) {
79+ // Either passed as an individual key:value argument, e.g.:
80+ // Excon.get(..., ssl_verify_peer: false)
81+ isSslVerifyPeerPair(arg.asExpr().getExpr(), value) and
82+ kvNode = arg
83+ or
84+ // Or as a single hash argument, e.g.:
85+ // Excon.get(..., { ssl_verify_peer: false, ... })
86+ exists(DataFlow::LocalSourceNode optionsNode, Pair p |
87+ p = optionsNode.asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
88+ isSslVerifyPeerPair(p, value) and
89+ optionsNode.flowsTo(arg) and
90+ kvNode.asExpr().getExpr() = p
91+ )
92+ }
93+
94+ /**
95+ * Holds if `callNode` sets `Excon.defaults[:ssl_verify_peer]` or
96+ * `Excon.ssl_verify_peer` to `value`.
97+ */
98+ private predicate setsDefaultVerification(DataFlow::CallNode callNode, boolean value) {
99+ callNode = API::getTopLevelMember("Excon").getReturn("defaults").getAMethodCall("[]=") and
100+ isSslVerifyPeerLiteral(callNode.getArgument(0)) and
101+ hasBooleanValue(callNode.getArgument(1), value)
102+ or
103+ callNode = API::getTopLevelMember("Excon").getAMethodCall("ssl_verify_peer=") and
104+ hasBooleanValue(callNode.getArgument(0), value)
105+ }
106+
107+ private predicate isSslVerifyPeerLiteral(DataFlow::Node node) {
108+ exists(DataFlow::LocalSourceNode literal |
109+ literal.asExpr().getExpr().(SymbolLiteral).getValueText() = "ssl_verify_peer" and
110+ literal.flowsTo(node)
111+ )
112+ }
113+
114+ /** Holds if `node` can contain `value`. */
115+ private predicate hasBooleanValue(DataFlow::Node node, boolean value) {
116+ exists(DataFlow::LocalSourceNode literal |
117+ literal.asExpr().getExpr().(BooleanLiteral).getValue() = value and
118+ literal.flowsTo(node)
119+ )
120+ }
121+
122+ /** Holds if `p` is the pair `ssl_verify_peer: <value>`. */
123+ private predicate isSslVerifyPeerPair(Pair p, boolean value) {
124+ exists(DataFlow::Node key, DataFlow::Node valueNode |
125+ key.asExpr().getExpr() = p.getKey() and valueNode.asExpr().getExpr() = p.getValue()
126+ |
127+ isSslVerifyPeerLiteral(key) and
128+ hasBooleanValue(valueNode, value)
129+ )
130+ }
0 commit comments