Skip to content

Commit 65337ef

Browse files
authored
Merge pull request #564 from taus-semmle/python-insecure-ssl-version
Python: Check for insecure versions of SSL and TLS.
2 parents 370a9e4 + 41836cd commit 65337ef

File tree

15 files changed

+363
-0
lines changed

15 files changed

+363
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Improvements to Python analysis
2+
3+
4+
## General improvements
5+
6+
> Changes that affect alerts in many files or from many queries
7+
> For example, changes to file classification
8+
## New queries
9+
10+
| **Query** | **Tags** | **Purpose** |
11+
|-----------------------------|-----------|--------------------------------------------------------------------|
12+
| Default version of SSL/TLS may be insecure (`py/insecure-default-protocol`) | security, external/cwe/cwe-327 | Finds instances where an insecure default protocol may be used. Results are shown on LGTM by default. |
13+
| Use of insecure SSL/TLS version (`py/insecure-protocol`) | security, external/cwe/cwe-327 | Finds instances where a known insecure protocol has been specified. Results are shown on LGTM by default. |
14+
15+
## Changes to existing queries
16+
17+
| **Query** | **Expected impact** | **Change** |
18+
|----------------------------|------------------------|------------------------------------------------------------------|
19+
20+
## Changes to code extraction
21+
22+
* *Series of bullet points*
23+
24+
## Changes to QL libraries
25+
26+
* *Series of bullet points*
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<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.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
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+
above is strongly recommended. If no explicit
18+
<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.
21+
</p>
22+
23+
</recommendation>
24+
<example>
25+
26+
<p>
27+
The following code shows two different ways of setting up a connection
28+
using SSL or TLS. They are both potentially insecure because the
29+
default version is used.
30+
</p>
31+
32+
<sample src="examples/insecure_default_protocol.py" />
33+
34+
<p>
35+
Both of the cases above should be updated to use a secure protocol
36+
instead, for instance by specifying
37+
<code>ssl_version=PROTOCOL_TLSv1_1</code> as a keyword argument.
38+
</p>
39+
<p>
40+
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.
44+
</p>
45+
</example>
46+
47+
<references>
48+
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"> Transport Layer Security</a>.</li>
49+
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext"> class ssl.SSLContext</a>.</li>
50+
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.wrap_socket"> ssl.wrap_socket</a>.</li>
51+
</references>
52+
53+
</qhelp>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @name Default version of SSL/TLS may be insecure
3+
* @description Leaving the SSL/TLS version unspecified may result in an insecure
4+
* default protocol being used.
5+
* @id py/insecure-default-protocol
6+
* @kind problem
7+
* @problem.severity warning
8+
* @precision high
9+
* @tags security
10+
* external/cwe/cwe-327
11+
*/
12+
13+
import python
14+
15+
FunctionObject ssl_wrap_socket() {
16+
result = any(ModuleObject ssl | ssl.getName() = "ssl").getAttribute("wrap_socket")
17+
}
18+
19+
ClassObject ssl_Context_class() {
20+
result = any(ModuleObject ssl | ssl.getName() = "ssl").getAttribute("SSLContext")
21+
}
22+
23+
CallNode unsafe_call(string method_name) {
24+
result = ssl_wrap_socket().getACall() and
25+
method_name = "deprecated method ssl.wrap_socket"
26+
or
27+
result = ssl_Context_class().getACall() and
28+
method_name = "ssl.SSLContext"
29+
}
30+
31+
from CallNode call, string method_name
32+
where
33+
call = unsafe_call(method_name) and
34+
not exists(call.getArgByName("ssl_version"))
35+
select call, "Call to " + method_name + " does not specify a protocol, which may result in an insecure default being used."
36+
37+
38+
39+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Using a broken or weak cryptographic protocol may make a connection
8+
vulnerable to interference from an attacker.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
14+
<p>
15+
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+
above is strongly recommended.
18+
</p>
19+
20+
</recommendation>
21+
<example>
22+
23+
<p>
24+
The following code shows a variety of ways of setting up a
25+
connection using SSL or TLS. They are all insecure because of the
26+
version specified.
27+
</p>
28+
29+
<sample src="examples/insecure_protocol.py" />
30+
31+
<p>
32+
All cases should be updated to use a secure protocol, such as
33+
<code>PROTOCOL_TLSv1_1</code>.
34+
</p>
35+
<p>
36+
Note that <code>ssl.wrap_socket</code> has been deprecated in
37+
Python 3.7. A preferred alternative is to use
38+
<code>ssl.SSLContext</code>, which is supported in Python 2.7.9 and
39+
3.2 and later versions.
40+
</p>
41+
</example>
42+
43+
<references>
44+
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"> Transport Layer Security</a>.</li>
45+
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext"> class ssl.SSLContext</a>.</li>
46+
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.wrap_socket"> ssl.wrap_socket</a>.</li>
47+
<li>pyOpenSSL documentation: <a href="https://pyopenssl.org/en/stable/api/ssl.html"> An interface to the SSL-specific parts of OpenSSL</a>.</li>
48+
</references>
49+
50+
</qhelp>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @name Use of insecure SSL/TLS version
3+
* @description Using an insecure SSL/TLS version may leave the connection vulnerable to attacks.
4+
* @id py/insecure-protocol
5+
* @kind problem
6+
* @problem.severity warning
7+
* @precision high
8+
* @tags security
9+
* external/cwe/cwe-327
10+
*/
11+
12+
import python
13+
14+
FunctionObject ssl_wrap_socket() {
15+
result = the_ssl_module().getAttribute("wrap_socket")
16+
}
17+
18+
ClassObject ssl_Context_class() {
19+
result = the_ssl_module().getAttribute("SSLContext")
20+
}
21+
22+
string insecure_version_name() {
23+
// For `pyOpenSSL.SSL`
24+
result = "SSLv2_METHOD" or
25+
result = "SSLv23_METHOD" or
26+
result = "SSLv3_METHOD" or
27+
result = "TLSv1_METHOD" or
28+
// For the `ssl` module
29+
result = "PROTOCOL_SSLv2" or
30+
result = "PROTOCOL_SSLv3" or
31+
result = "PROTOCOL_SSLv23" or
32+
result = "PROTOCOL_TLS" or
33+
result = "PROTOCOL_TLSv1"
34+
}
35+
36+
private ModuleObject the_ssl_module() {
37+
result = any(ModuleObject m | m.getName() = "ssl")
38+
}
39+
40+
private ModuleObject the_pyOpenSSL_module() {
41+
result = any(ModuleObject m | m.getName() = "pyOpenSSL.SSL")
42+
}
43+
44+
/* A syntactic check for cases where points-to analysis cannot infer the presence of
45+
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
46+
* library.
47+
*/
48+
predicate probable_insecure_ssl_constant(CallNode call, string insecure_version) {
49+
exists(ControlFlowNode arg | arg = call.getArgByName("ssl_version") |
50+
arg.(AttrNode).getObject(insecure_version).refersTo(the_ssl_module())
51+
or
52+
arg.(NameNode).getId() = insecure_version and
53+
exists(Import imp |
54+
imp.getAnImportedModuleName() = "ssl" and
55+
imp.getAName().getAsname().(Name).getId() = insecure_version
56+
)
57+
)
58+
}
59+
60+
predicate unsafe_ssl_wrap_socket_call(CallNode call, string method_name, string insecure_version) {
61+
(
62+
call = ssl_wrap_socket().getACall() and
63+
method_name = "deprecated method ssl.wrap_socket"
64+
or
65+
call = ssl_Context_class().getACall() and
66+
method_name = "ssl.SSLContext"
67+
)
68+
and
69+
insecure_version = insecure_version_name()
70+
and
71+
(
72+
call.getArgByName("ssl_version").refersTo(the_ssl_module().getAttribute(insecure_version))
73+
or
74+
probable_insecure_ssl_constant(call, insecure_version)
75+
)
76+
}
77+
78+
ClassObject the_pyOpenSSL_Context_class() {
79+
result = any(ModuleObject m | m.getName() = "pyOpenSSL.SSL").getAttribute("Context")
80+
}
81+
82+
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
83+
call = the_pyOpenSSL_Context_class().getACall() and
84+
insecure_version = insecure_version_name() and
85+
call.getArg(0).refersTo(the_pyOpenSSL_module().getAttribute(insecure_version))
86+
}
87+
88+
from CallNode call, string method_name, string insecure_version
89+
where
90+
unsafe_ssl_wrap_socket_call(call, method_name, insecure_version)
91+
or
92+
unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
93+
select call, "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name + "."
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import ssl
2+
import socket
3+
4+
# Using the deprecated ssl.wrap_socket method
5+
ssl.wrap_socket(socket.socket())
6+
7+
# Using SSLContext
8+
context = ssl.SSLContext()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import ssl
2+
import socket
3+
4+
# Using the deprecated ssl.wrap_socket method
5+
ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_SSLv2)
6+
7+
# Using SSLContext
8+
context = ssl.SSLContext(ssl_version=ssl.PROTOCOL_SSLv3)
9+
10+
# Using pyOpenSSL
11+
12+
from pyOpenSSL import SSL
13+
14+
context = SSL.Context(SSL.TLSv1_METHOD)
15+
16+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| InsecureProtocol.py:41:1:41: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. |
2+
| InsecureProtocol.py:42:11:42:22 | ControlFlowNode for SSLContext() | Call to ssl.SSLContext does not specify a protocol, which may result in an insecure default being used. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-327/InsecureDefaultProtocol.ql
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
| InsecureProtocol.py:6:1:6:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to deprecated method ssl.wrap_socket. |
2+
| InsecureProtocol.py:7:1:7:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv3 specified in call to deprecated method ssl.wrap_socket. |
3+
| InsecureProtocol.py:8:1:8:47 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_TLSv1 specified in call to deprecated method ssl.wrap_socket. |
4+
| InsecureProtocol.py:10:1:10:42 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |
5+
| InsecureProtocol.py:11:1:11:42 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv3 specified in call to ssl.SSLContext. |
6+
| InsecureProtocol.py:12:1:12:42 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_TLSv1 specified in call to ssl.SSLContext. |
7+
| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
8+
| InsecureProtocol.py:15:1:15:30 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv23_METHOD specified in call to pyOpenSSL.SSL.Context. |
9+
| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3_METHOD specified in call to pyOpenSSL.SSL.Context. |
10+
| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1_METHOD specified in call to pyOpenSSL.SSL.Context. |
11+
| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
12+
| InsecureProtocol.py:48:1:48:43 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to deprecated method ssl.wrap_socket. |
13+
| InsecureProtocol.py:49:1:49:38 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |

0 commit comments

Comments
 (0)