1010 */
1111
1212import python
13+ import semmle.python.ApiGraphs
1314
15+ // The idea is to track flow from the creation of an insecure context to a use
16+ // such as `wrap_socket`. There should be a data-flow path for each insecure version
17+ // and each path should have a version specific sanitizer. This will allow fluent api
18+ // style code to block the paths one by one.
19+ //
20+ // class InsecureContextCreation extends DataFlow::CfgNode {
21+ // override CallNode node;
22+ // InsecureContextCreation() {
23+ // this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
24+ // insecure_version().asCfgNode() in [node.getArg(0), node.getArgByName("protocol")]
25+ // }
26+ // }
27+ // class InsecureSSLContextCreation extends DataFlow::CfgNode {
28+ // override CallNode node;
29+ // InsecureSSLContextCreation() {
30+ // this = API::moduleImport("ssl").getMember("create_default_context").getACall()
31+ // or
32+ // this = API::moduleImport("ssl").getMember("SSLContext").getACall() and
33+ // API::moduleImport("ssl").getMember("PROTOCOL_TLS").getAUse().asCfgNode() in [
34+ // node.getArg(0), node.getArgByName("protocol")
35+ // ]
36+ // }
37+ // }
38+ abstract class ContextCreation extends DataFlow:: CfgNode {
39+ abstract DataFlow:: CfgNode getProtocol ( ) ;
40+ }
41+
42+ class SSLContextCreation extends ContextCreation {
43+ override CallNode node ;
44+
45+ SSLContextCreation ( ) { this = API:: moduleImport ( "ssl" ) .getMember ( "SSLContext" ) .getACall ( ) }
46+
47+ override DataFlow:: CfgNode getProtocol ( ) {
48+ result .getNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "protocol" ) ]
49+ }
50+ }
51+
52+ class PyOpenSSLContextCreation extends ContextCreation {
53+ override CallNode node ;
54+
55+ PyOpenSSLContextCreation ( ) {
56+ this = API:: moduleImport ( "pyOpenSSL" ) .getMember ( "SSL" ) .getMember ( "Context" ) .getACall ( )
57+ }
58+
59+ override DataFlow:: CfgNode getProtocol ( ) {
60+ result .getNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "method" ) ]
61+ }
62+ }
63+
64+ abstract class ConnectionCreation extends DataFlow:: CfgNode {
65+ abstract DataFlow:: CfgNode getContext ( ) ;
66+ }
67+
68+ class WrapSocketCall extends ConnectionCreation {
69+ override CallNode node ;
70+
71+ WrapSocketCall ( ) { node .getFunction ( ) .( AttrNode ) .getName ( ) = "wrap_socket" }
72+
73+ override DataFlow:: CfgNode getContext ( ) {
74+ result .getNode ( ) = node .getFunction ( ) .( AttrNode ) .getObject ( )
75+ }
76+ }
77+
78+ class ConnectionCall extends ConnectionCreation {
79+ override CallNode node ;
80+
81+ ConnectionCall ( ) {
82+ this = API:: moduleImport ( "pyOpenSSL" ) .getMember ( "SSL" ) .getMember ( "Connection" ) .getACall ( )
83+ }
84+
85+ override DataFlow:: CfgNode getContext ( ) {
86+ result .getNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "context" ) ]
87+ }
88+ }
89+
90+ abstract class TlsLibrary extends string {
91+ TlsLibrary ( ) { this in [ "ssl" ] }
92+
93+ abstract string specific_insecure_version_name ( ) ;
94+
95+ abstract string unspecific_version_name ( ) ;
96+
97+ abstract API:: Node version_constants ( ) ;
98+
99+ DataFlow:: Node insecure_version ( ) {
100+ result = version_constants ( ) .getMember ( specific_insecure_version_name ( ) ) .getAUse ( )
101+ }
102+
103+ DataFlow:: Node unspecific_version ( ) {
104+ result = version_constants ( ) .getMember ( unspecific_version_name ( ) ) .getAUse ( )
105+ }
106+
107+ abstract DataFlow:: CfgNode default_context_creation ( ) ;
108+
109+ abstract ContextCreation specific_context_creation ( ) ;
110+
111+ ContextCreation insecure_context_creation ( ) {
112+ result = specific_context_creation ( ) and
113+ result .getProtocol ( ) = insecure_version ( )
114+ }
115+
116+ DataFlow:: CfgNode unspecific_context_creation ( ) {
117+ result = default_context_creation ( )
118+ or
119+ result = specific_context_creation ( ) and
120+ result .( ContextCreation ) .getProtocol ( ) = unspecific_version ( )
121+ }
122+
123+ abstract ConnectionCreation connection_creation ( ) ;
124+ }
125+
126+ class Ssl extends TlsLibrary {
127+ Ssl ( ) { this = "ssl" }
128+
129+ override string specific_insecure_version_name ( ) {
130+ result in [
131+ "PROTOCOL_SSLv2" , "PROTOCOL_SSLv3" , "PROTOCOL_SSLv23" , "PROTOCOL_TLSv1" , "PROTOCOL_TLSv1_1"
132+ ]
133+ }
134+
135+ override string unspecific_version_name ( ) { result = "PROTOCOL_TLS" }
136+
137+ override API:: Node version_constants ( ) { result = API:: moduleImport ( "ssl" ) }
138+
139+ override DataFlow:: CfgNode default_context_creation ( ) {
140+ result = API:: moduleImport ( "ssl" ) .getMember ( "create_default_context" ) .getACall ( )
141+ }
142+
143+ override ContextCreation specific_context_creation ( ) { result instanceof SSLContextCreation }
144+
145+ override ConnectionCreation connection_creation ( ) { result instanceof WrapSocketCall }
146+ }
147+
148+ class PyOpenSSL extends TlsLibrary {
149+ PyOpenSSL ( ) { this = "pyOpenSSL" }
150+
151+ override string specific_insecure_version_name ( ) {
152+ result in [ "SSLv2_METHOD" , "SSLv23_METHOD" , "SSLv3_METHOD" , "TLSv1_METHOD" , "TLSv1_1_METHOD" ]
153+ }
154+
155+ override string unspecific_version_name ( ) { result = "TLS_METHOD" }
156+
157+ override API:: Node version_constants ( ) {
158+ result = API:: moduleImport ( "pyOpenSSL" ) .getMember ( "SSL" )
159+ }
160+
161+ override DataFlow:: CfgNode default_context_creation ( ) { none ( ) }
162+
163+ override ContextCreation specific_context_creation ( ) {
164+ result instanceof PyOpenSSLContextCreation
165+ }
166+
167+ override ConnectionCreation connection_creation ( ) { result instanceof ConnectionCall }
168+ }
169+
170+ module ssl {
171+ string insecure_version_name ( ) {
172+ result = "PROTOCOL_SSLv2" or
173+ result = "PROTOCOL_SSLv3" or
174+ result = "PROTOCOL_SSLv23" or
175+ result = "PROTOCOL_TLSv1" or
176+ result = "PROTOCOL_TLSv1_1"
177+ }
178+
179+ DataFlow:: Node insecure_version ( ) {
180+ result = API:: moduleImport ( "ssl" ) .getMember ( insecure_version_name ( ) ) .getAUse ( )
181+ }
182+ }
183+
184+ module pyOpenSSL {
185+ string insecure_version_name ( ) {
186+ result = "SSLv2_METHOD" or
187+ result = "SSLv23_METHOD" or
188+ result = "SSLv3_METHOD" or
189+ result = "TLSv1_METHOD" or
190+ result = "TLSv1_1_METHOD"
191+ }
192+
193+ DataFlow:: Node insecure_version ( ) {
194+ result =
195+ API:: moduleImport ( "pyOpenSSL" ) .getMember ( "SSL" ) .getMember ( insecure_version_name ( ) ) .getAUse ( )
196+ }
197+ }
198+
199+ class InsecureContextConfiguration extends DataFlow:: Configuration {
200+ TlsLibrary library ;
201+
202+ InsecureContextConfiguration ( ) { this = library + [ "AllowsTLSv1" , "AllowsTLSv1_1" ] }
203+
204+ override predicate isSource ( DataFlow:: Node source ) {
205+ source = library .unspecific_context_creation ( )
206+ }
207+
208+ override predicate isSink ( DataFlow:: Node sink ) {
209+ sink = library .connection_creation ( ) .getContext ( )
210+ }
211+
212+ abstract string flag ( ) ;
213+
214+ override predicate isBarrierOut ( DataFlow:: Node node ) {
215+ exists ( AugAssign aa , AttrNode attr |
216+ aa .getOperation ( ) .getOp ( ) instanceof BitOr and
217+ aa .getTarget ( ) = attr .getNode ( ) and
218+ attr .getName ( ) = "options" and
219+ attr .getObject ( ) = node .asCfgNode ( ) and
220+ aa .getValue ( ) = API:: moduleImport ( "ssl" ) .getMember ( flag ( ) ) .getAUse ( ) .asExpr ( )
221+ )
222+ }
223+ }
224+
225+ class AllowsTLSv1 extends InsecureContextConfiguration {
226+ AllowsTLSv1 ( ) { this = library + "AllowsTLSv1" }
227+
228+ override string flag ( ) { result = "OP_NO_TLSv1" }
229+ }
230+
231+ class AllowsTLSv1_1 extends InsecureContextConfiguration {
232+ AllowsTLSv1_1 ( ) { this = library + "AllowsTLSv1_1" }
233+
234+ override string flag ( ) { result = "OP_NO_TLSv1_2" }
235+ }
236+
237+ predicate unsafe_connection_creation ( DataFlow:: Node node ) {
238+ exists ( AllowsTLSv1 c | c .hasFlowTo ( node ) ) or
239+ exists ( AllowsTLSv1_1 c | c .hasFlowTo ( node ) ) //or
240+ // node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
241+ }
242+
243+ predicate unsafe_context_creation ( DataFlow:: Node node ) {
244+ exists ( TlsLibrary l | l .insecure_context_creation ( ) = node )
245+ }
246+
247+ // class InsecureTLSContextConfiguration extends DataFlow::Configuration {
248+ // InsecureTLSContextConfiguration() { this in ["AllowsTLSv1", "AllowsTLSv1_1"] }
249+ // override predicate isSource(DataFlow::Node source) {
250+ // source instanceof InsecureSSLContextCreation
251+ // }
252+ // override predicate isSink(DataFlow::Node sink) { sink = any(WrapSocketCall c).getContext() }
253+ // abstract string flag();
254+ // override predicate isBarrierOut(DataFlow::Node node) {
255+ // exists(AugAssign aa, AttrNode attr |
256+ // aa.getOperation().getOp() instanceof BitOr and
257+ // aa.getTarget() = attr.getNode() and
258+ // attr.getName() = "options" and
259+ // attr.getObject() = node.asCfgNode() and
260+ // aa.getValue() = API::moduleImport("ssl").getMember(flag()).getAUse().asExpr()
261+ // )
262+ // }
263+ // }
264+ // class AllowsTLSv1 extends InsecureTLSContextConfiguration {
265+ // AllowsTLSv1() { this = "AllowsTLSv1" }
266+ // override string flag() { result = "OP_NO_TLSv1" }
267+ // }
268+ // class AllowsTLSv1_1 extends InsecureTLSContextConfiguration {
269+ // AllowsTLSv1_1() { this = "AllowsTLSv1_1" }
270+ // override string flag() { result = "OP_NO_TLSv1_1" }
271+ // }
272+ // predicate unsafe_wrap_socket_call(DataFlow::Node node) {
273+ // exists(AllowsTLSv1 c | c.hasFlowTo(node)) or
274+ // exists(AllowsTLSv1_1 c | c.hasFlowTo(node)) or
275+ // node = API::moduleImport("ssl").getMember("wrap_socket").getACall()
276+ // }
14277private ModuleValue the_ssl_module ( ) { result = Module:: named ( "ssl" ) }
15278
16279FunctionValue ssl_wrap_socket ( ) { result = the_ssl_module ( ) .attr ( "wrap_socket" ) }
@@ -21,20 +284,24 @@ private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SS
21284
22285ClassValue the_pyOpenSSL_Context_class ( ) { result = Value:: named ( "pyOpenSSL.SSL.Context" ) }
23286
24- string insecure_version_name ( ) {
25- // For `pyOpenSSL.SSL`
26- result = "SSLv2_METHOD" or
27- result = "SSLv23_METHOD" or
28- result = "SSLv3_METHOD" or
29- result = "TLSv1_METHOD" or
30- // For the `ssl` module
31- result = "PROTOCOL_SSLv2" or
32- result = "PROTOCOL_SSLv3" or
33- result = "PROTOCOL_SSLv23" or
34- result = "PROTOCOL_TLS" or
35- result = "PROTOCOL_TLSv1"
36- }
37-
287+ // Since version 3.6, it is fine to call `ssl.SSLContext(protocol=PROTOCOL_TLS)`
288+ // if one also specifies either OP_NO_TLSv1 (introduced in 3.2)
289+ // or SSLContext.minimum_version other than TLSVersion.TLSv1 (introduced in 3.7)
290+ // See https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
291+ // and https://docs.python.org/3/library/ssl.html?highlight=ssl#protocol-versions
292+ // FP reported here: https://github.com/github/codeql/issues/2554
293+ // string insecure_version_name() {
294+ // // For `pyOpenSSL.SSL`
295+ // result = "SSLv2_METHOD" or
296+ // result = "SSLv23_METHOD" or
297+ // result = "SSLv3_METHOD" or
298+ // result = "TLSv1_METHOD" or
299+ // // For the `ssl` module
300+ // result = "PROTOCOL_SSLv2" or
301+ // result = "PROTOCOL_SSLv3" or
302+ // result = "PROTOCOL_SSLv23" or
303+ // result = "PROTOCOL_TLSv1"
304+ // }
38305/*
39306 * A syntactic check for cases where points-to analysis cannot infer the presence of
40307 * a protocol constant, e.g. if it has been removed in later versions of the `ssl`
@@ -71,7 +338,7 @@ predicate unsafe_ssl_wrap_socket_call(
71338 named_argument = "protocol" and
72339 method_name = "ssl.SSLContext"
73340 ) and
74- insecure_version = insecure_version_name ( ) and
341+ insecure_version = ssl :: insecure_version_name ( ) and
75342 (
76343 call .getArgByName ( named_argument ) .pointsTo ( the_ssl_module ( ) .attr ( insecure_version ) )
77344 or
@@ -81,7 +348,7 @@ predicate unsafe_ssl_wrap_socket_call(
81348
82349predicate unsafe_pyOpenSSL_Context_call ( CallNode call , string insecure_version ) {
83350 call = the_pyOpenSSL_Context_class ( ) .getACall ( ) and
84- insecure_version = insecure_version_name ( ) and
351+ insecure_version = pyOpenSSL :: insecure_version_name ( ) and
85352 call .getArg ( 0 ) .pointsTo ( the_pyOpenSSL_module ( ) .attr ( insecure_version ) )
86353}
87354
0 commit comments