@@ -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