Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 86dde6e

Browse files
committed
Python: start of port
1 parent 2b54c33 commit 86dde6e

3 files changed

Lines changed: 307 additions & 16 deletions

File tree

python/ql/src/Security/CWE-327/InsecureProtocol.ql

Lines changed: 283 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,270 @@
1010
*/
1111

1212
import 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+
// }
14277
private ModuleValue the_ssl_module() { result = Module::named("ssl") }
15278

16279
FunctionValue 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

22285
ClassValue 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

82349
predicate 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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# taken from https://docs.python.org/3/library/ssl.html?highlight=ssl#ssl.SSLContext
2+
3+
import socket
4+
import ssl
5+
6+
hostname = 'www.python.org'
7+
context = ssl.create_default_context()
8+
context.options |= ssl.OP_NO_TLSv1 # This added by me
9+
context.options |= ssl.OP_NO_TLSv1_1 # This added by me
10+
11+
with socket.create_connection((hostname, 443)) as sock:
12+
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
13+
print(ssock.version())
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import socket
2+
import ssl
3+
4+
hostname = 'www.python.org'
5+
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
6+
context.options |= ssl.OP_NO_TLSv1
7+
context.options |= ssl.OP_NO_TLSv1_1 # This added by me
8+
9+
with socket.create_connection((hostname, 443)) as sock:
10+
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
11+
print(ssock.version())

0 commit comments

Comments
 (0)