@@ -7,6 +7,7 @@ private import codeql.ruby.CFG
7
7
private import codeql.ruby.Concepts
8
8
private import codeql.ruby.ApiGraphs
9
9
private import codeql.ruby.DataFlow
10
+ private import codeql.ruby.dataflow.internal.DataFlowImplForLibraries as DataFlowImplForLibraries
10
11
11
12
/**
12
13
* A call that makes an HTTP request using `Excon`.
@@ -61,96 +62,87 @@ class ExconHttpRequest extends HTTP::Client::Request::Range, DataFlow::CallNode
61
62
result = connectionUse .( DataFlow:: CallNode ) .getArgument ( 0 )
62
63
}
63
64
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
83
75
|
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 ( )
85
86
)
86
87
)
87
88
}
88
89
89
90
override predicate disablesCertificateValidation (
90
91
DataFlow:: Node disablingNode , DataFlow:: Node argumentOrigin
91
92
) {
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
+ )
94
130
}
95
131
96
132
override string getFramework ( ) { result = "Excon" }
97
133
}
98
134
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
+ }
139
140
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
+ }
147
144
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
+ }
156
148
}
0 commit comments