Skip to content

Commit 5097836

Browse files
authored
Merge pull request github#5246 from yoff/python-port-insecure-default-protocol
Python: Port insecure default protocol
2 parents aa360c0 + fe975f2 commit 5097836

File tree

9 files changed

+67
-31
lines changed

9 files changed

+67
-31
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Updated the query that detects insecure SSL/TLS protocol creation from default values (`py/insecure-default-protocol`) to use the new API graphs. The query also no longer reports use of the default value for constructing `ssl.SSLContext`, since that _can_ still be secure, either through manipulation of the `options` field or the `minimum_version` field. If the usage is not secure, this should be reported by the `py/insecure-protocol` query.

python/ql/src/Security/CWE-327/InsecureDefaultProtocol.qhelp

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,22 @@
33
"qhelp.dtd">
44
<qhelp>
55
<overview>
6-
<p> The <code>ssl</code> library defaults to an insecure version of
7-
SSL/TLS when no specific protocol version is specified. This may leave
8-
the connection vulnerable to attack.
6+
<p>
7+
The <code>ssl.wrap_socket</code> function defaults
8+
to an insecure version of SSL/TLS when no specific protocol version is
9+
specified. This may leave the connection vulnerable to attack.
910
</p>
1011

1112
</overview>
1213
<recommendation>
1314

1415
<p>
1516
Ensure that a modern, strong protocol is used. All versions of SSL,
16-
and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
17+
and TLS 1.0 and 1.1 are known to be vulnerable to attacks. Using TLS 1.2 or
1718
above is strongly recommended. If no explicit
1819
<code>ssl_version</code> is specified, the default
19-
<code>PROTOCOL_TLS</code> is chosen. This protocol is insecure and
20-
should not be used.
20+
<code>PROTOCOL_TLS</code> is chosen. This protocol is insecure because it
21+
allows TLS 1.0 and TLS 1.1 and so should not be used.
2122
</p>
2223

2324
</recommendation>
@@ -34,20 +35,43 @@
3435
<p>
3536
Both of the cases above should be updated to use a secure protocol
3637
instead, for instance by specifying
37-
<code>ssl_version=PROTOCOL_TLSv1_1</code> as a keyword argument.
38+
<code>ssl_version=PROTOCOL_TLSv1_2</code> as a keyword argument.
39+
</p>
40+
<p>
41+
The latter example can also be made secure by modifying the created
42+
context before it is used to create a connection. Therefore it will not be
43+
flagged by this query. However, if a connection is created before
44+
the context has been secured (for example, by setting the value of <code>minimum_version</code>),
45+
then the code should be flagged by the query <code>py/insecure-protocol</code>.
3846
</p>
3947
<p>
4048
Note that <code>ssl.wrap_socket</code> has been deprecated in
41-
Python 3.7. A preferred alternative is to use
42-
<code>ssl.SSLContext</code>, which is supported in Python 2.7.9 and
43-
3.2 and later versions.
49+
Python 3.7. The recommended alternatives are:
4450
</p>
51+
<ul>
52+
<li><code>ssl.SSLContext</code> - supported in Python 2.7.9,
53+
3.2, and later versions</li>
54+
<li><code>ssl.create_default_context</code> - a convenience function,
55+
supported in Python 3.4 and later versions.</li>
56+
</ul>
57+
58+
<p>
59+
Even when you use these alternatives, you should
60+
ensure that a safe protocol is used. The following code illustrates
61+
how to use flags (available since Python 3.2) or the `minimum_version`
62+
field (favored since Python 3.7) to restrict the protocols accepted when
63+
creating a connection.
64+
</p>
65+
66+
<sample src="examples/secure_default_protocol.py" />
4567
</example>
4668

4769
<references>
4870
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"> Transport Layer Security</a>.</li>
4971
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext"> class ssl.SSLContext</a>.</li>
5072
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.wrap_socket"> ssl.wrap_socket</a>.</li>
73+
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#functions-constants-and-exceptions"> notes on context creation</a>.</li>
74+
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl-security"> notes on security considerations</a>.</li>
5175
</references>
5276

5377
</qhelp>

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

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,19 @@
1010
* external/cwe/cwe-327
1111
*/
1212

13+
// Connections are generally created based on a context which controls the range of acceptable
14+
// protocols. This query reports the deprecated way of creating connections without referring
15+
// to a context (via `ssl.wrap_socket`). Doing this and not specifying which protocols are
16+
// acceptable means that connections will be created with the insecure default settings.
17+
//
18+
// Detecting that a connection is created with a context that has not been suitably configured
19+
// is handled by the data-flow query py/insecure-protocol.
1320
import python
21+
import semmle.python.ApiGraphs
1422

15-
FunctionValue ssl_wrap_socket() { result = Value::named("ssl.wrap_socket") }
16-
17-
ClassValue ssl_Context_class() { result = Value::named("ssl.SSLContext") }
18-
19-
CallNode unsafe_call(string method_name) {
20-
result = ssl_wrap_socket().getACall() and
21-
not exists(result.getArgByName("ssl_version")) and
22-
method_name = "deprecated method ssl.wrap_socket"
23-
or
24-
result = ssl_Context_class().getACall() and
25-
not exists(result.getArgByName("protocol")) and
26-
not exists(result.getArg(0)) and
27-
method_name = "ssl.SSLContext"
28-
}
29-
30-
from CallNode call, string method_name
31-
where call = unsafe_call(method_name)
23+
from DataFlow::CallCfgNode call
24+
where
25+
call = API::moduleImport("ssl").getMember("wrap_socket").getACall() and
26+
not exists(call.getArgByName("ssl_version"))
3227
select call,
33-
"Call to " + method_name +
34-
" does not specify a protocol, which may result in an insecure default being used."
28+
"Call to deprecated method ssl.wrap_socket does not specify a protocol, which may result in an insecure default being used."
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import ssl
2+
3+
# Using flags to restrict the protocol
4+
context = ssl.SSLContext()
5+
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
6+
7+
# Declaring a minimum version to restrict the protocol
8+
context = ssl.create_default_context()
9+
context.minimum_version = ssl.TLSVersion.TLSv1_2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| InsecureProtocol.py:7:1:7:17 | ControlFlowNode for Attribute() | Call to deprecated method ssl.wrap_socket does not specify a protocol, which may result in an insecure default being used. |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ssl
2+
3+
# secure versions
4+
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_2)
5+
6+
# possibly insecure default
7+
ssl.wrap_socket()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
semmle-extractor-options: --lang=2 --max-import-depth=1

python/ql/test/query-tests/Security/CWE-327/InsecureDefaultProtocol.expected

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)