@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
77private import codeql.ruby.Concepts
88private import codeql.ruby.ApiGraphs
99private 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 `Excon`.
@@ -61,96 +62,87 @@ class ExconHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode
6162 result = connectionUse .( DataFlow:: CallNode ) .getArgument ( 0 )
6263 }
6364
64- override predicate disablesCertificateValidation ( DataFlow:: Node disablingNode ) {
65- // Check for `ssl_verify_peer: false` in the options hash.
66- exists ( DataFlow:: Node arg , int i |
67- i > 0 and
68- arg = connectionNode .getAValueReachableFromSource ( ) .( DataFlow:: CallNode ) .getArgument ( i )
69- |
70- argSetsVerifyPeer ( arg , false , disablingNode )
71- )
72- or
73- // Or we see a call to `Excon.defaults[:ssl_verify_peer] = false` before the
74- // request, and no `ssl_verify_peer: true` in the explicit options hash for
75- // the request call.
76- exists ( DataFlow:: CallNode disableCall |
77- setsDefaultVerification ( disableCall , false ) and
78- disableCall .asExpr ( ) .getASuccessor + ( ) = this .asExpr ( ) and
79- disablingNode = disableCall and
80- not exists ( DataFlow:: Node arg , int i |
81- i > 0 and
82- arg = connectionNode .getAValueReachableFromSource ( ) .( DataFlow:: CallNode ) .getArgument ( i )
65+ /** Gets the value that controls certificate validation, if any. */
66+ DataFlow:: Node getCertificateValidationControllingValue ( ) {
67+ exists ( DataFlow:: CallNode newCall | newCall = connectionNode .getAValueReachableFromSource ( ) |
68+ // Check for `ssl_verify_peer: false`
69+ result = newCall .getKeywordArgument ( "ssl_verify_peer" )
70+ or
71+ // using a hashliteral
72+ exists (
73+ DataFlow:: LocalSourceNode optionsNode , CfgNodes:: ExprNodes:: PairCfgNode p ,
74+ DataFlow:: Node key
8375 |
84- argSetsVerifyPeer ( arg , true , _)
76+ // can't flow to argument 0, since that's the URL
77+ optionsNode .flowsTo ( newCall .getArgument ( any ( int i | i > 0 ) ) ) and
78+ p = optionsNode .asExpr ( ) .( CfgNodes:: ExprNodes:: HashLiteralCfgNode ) .getAKeyValuePair ( ) and
79+ key .asExpr ( ) = p .getKey ( ) and
80+ key .getALocalSource ( )
81+ .asExpr ( )
82+ .getExpr ( )
83+ .getConstantValue ( )
84+ .isStringlikeValue ( "ssl_verify_peer" ) and
85+ result .asExpr ( ) = p .getValue ( )
8586 )
8687 )
8788 }
8889
8990 override predicate disablesCertificateValidation (
9091 DataFlow:: Node disablingNode , DataFlow:: Node argumentOrigin
9192 ) {
92- disablesCertificateValidation ( disablingNode ) and
93- argumentOrigin = disablingNode
93+ any ( ExconDisablesCertificateValidationConfiguration config )
94+ .hasFlow ( argumentOrigin , disablingNode ) and
95+ disablingNode = this .getCertificateValidationControllingValue ( )
96+ or
97+ // We set `Excon.defaults[:ssl_verify_peer]` or `Excon.ssl_verify_peer` = false`
98+ // before the request, and no `ssl_verify_peer: true` in the explicit options hash
99+ // for the request call.
100+ exists ( DataFlow:: CallNode disableCall , BooleanLiteral value |
101+ // Excon.defaults[:ssl_verify_peer]
102+ disableCall = API:: getTopLevelMember ( "Excon" ) .getReturn ( "defaults" ) .getAMethodCall ( "[]=" ) and
103+ disableCall
104+ .getArgument ( 0 )
105+ .getALocalSource ( )
106+ .asExpr ( )
107+ .getExpr ( )
108+ .getConstantValue ( )
109+ .isStringlikeValue ( "ssl_verify_peer" ) and
110+ disablingNode = disableCall .getArgument ( 1 ) and
111+ argumentOrigin = disablingNode .getALocalSource ( ) and
112+ value = argumentOrigin .asExpr ( ) .getExpr ( )
113+ or
114+ // Excon.ssl_verify_peer
115+ disableCall = API:: getTopLevelMember ( "Excon" ) .getAMethodCall ( "ssl_verify_peer=" ) and
116+ disablingNode = disableCall .getArgument ( 0 ) and
117+ argumentOrigin = disablingNode .getALocalSource ( ) and
118+ value = argumentOrigin .asExpr ( ) .getExpr ( )
119+ |
120+ value .getValue ( ) = false and
121+ disableCall .asExpr ( ) .getASuccessor + ( ) = this .asExpr ( ) and
122+ // no `ssl_verify_peer: true` in the request call.
123+ not this .getCertificateValidationControllingValue ( )
124+ .getALocalSource ( )
125+ .asExpr ( )
126+ .getExpr ( )
127+ .( BooleanLiteral )
128+ .getValue ( ) = true
129+ )
94130 }
95131
96132 override string getFramework ( ) { result = "Excon" }
97133}
98134
99- /**
100- * Holds if `arg` represents an options hash that contains the key
101- * `:ssl_verify_peer` with `value`, where `kvNode` is the data-flow node for
102- * this key-value pair.
103- */
104- predicate argSetsVerifyPeer ( DataFlow:: Node arg , boolean value , DataFlow:: Node kvNode ) {
105- // Either passed as an individual key:value argument, e.g.:
106- // Excon.get(..., ssl_verify_peer: false)
107- isSslVerifyPeerPair ( arg .asExpr ( ) , value ) and
108- kvNode = arg
109- or
110- // Or as a single hash argument, e.g.:
111- // Excon.get(..., { ssl_verify_peer: false, ... })
112- exists ( DataFlow:: LocalSourceNode optionsNode , CfgNodes:: ExprNodes:: PairCfgNode p |
113- p = optionsNode .asExpr ( ) .( CfgNodes:: ExprNodes:: HashLiteralCfgNode ) .getAKeyValuePair ( ) and
114- isSslVerifyPeerPair ( p , value ) and
115- optionsNode .flowsTo ( arg ) and
116- kvNode .asExpr ( ) = p
117- )
118- }
119-
120- /**
121- * Holds if `callNode` sets `Excon.defaults[:ssl_verify_peer]` or
122- * `Excon.ssl_verify_peer` to `value`.
123- */
124- private predicate setsDefaultVerification ( DataFlow:: CallNode callNode , boolean value ) {
125- callNode = API:: getTopLevelMember ( "Excon" ) .getReturn ( "defaults" ) .getAMethodCall ( "[]=" ) and
126- isSslVerifyPeerLiteral ( callNode .getArgument ( 0 ) ) and
127- hasBooleanValue ( callNode .getArgument ( 1 ) , value )
128- or
129- callNode = API:: getTopLevelMember ( "Excon" ) .getAMethodCall ( "ssl_verify_peer=" ) and
130- hasBooleanValue ( callNode .getArgument ( 0 ) , value )
131- }
132-
133- private predicate isSslVerifyPeerLiteral ( DataFlow:: Node node ) {
134- exists ( DataFlow:: LocalSourceNode literal |
135- literal .asExpr ( ) .getExpr ( ) .getConstantValue ( ) .isStringlikeValue ( "ssl_verify_peer" ) and
136- literal .flowsTo ( node )
137- )
138- }
135+ /** A configuration to track values that can disable certificate validation for Excon. */
136+ private class ExconDisablesCertificateValidationConfiguration extends DataFlowImplForLibraries:: Configuration {
137+ ExconDisablesCertificateValidationConfiguration ( ) {
138+ this = "ExconDisablesCertificateValidationConfiguration"
139+ }
139140
140- /** Holds if `node` can contain `value`. */
141- private predicate hasBooleanValue ( DataFlow:: Node node , boolean value ) {
142- exists ( DataFlow:: LocalSourceNode literal |
143- literal .asExpr ( ) .getExpr ( ) .( BooleanLiteral ) .getValue ( ) = value and
144- literal .flowsTo ( node )
145- )
146- }
141+ override predicate isSource ( DataFlow:: Node source ) {
142+ source .asExpr ( ) .getExpr ( ) .( BooleanLiteral ) .isFalse ( )
143+ }
147144
148- /** Holds if `p` is the pair `ssl_verify_peer: <value>`. */
149- private predicate isSslVerifyPeerPair ( CfgNodes:: ExprNodes:: PairCfgNode p , boolean value ) {
150- exists ( DataFlow:: Node key , DataFlow:: Node valueNode |
151- key .asExpr ( ) = p .getKey ( ) and
152- valueNode .asExpr ( ) = p .getValue ( ) and
153- isSslVerifyPeerLiteral ( key ) and
154- hasBooleanValue ( valueNode , value )
155- )
145+ override predicate isSink ( DataFlow:: Node sink ) {
146+ sink = any ( ExconHttpRequest req ) .getCertificateValidationControllingValue ( )
147+ }
156148}
0 commit comments