Merge pull request #564 from taus-semmle/python-insecure-ssl-version

Python: Check for insecure versions of SSL and TLS.
This commit is contained in:
Mark Shannon
2019-01-16 12:32:30 +00:00
committed by GitHub
15 changed files with 363 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p> The <code>ssl</code> library defaults to an insecure version of
SSL/TLS when no specific protocol version is specified. This may leave
the connection vulnerable to attack.
</p>
</overview>
<recommendation>
<p>
Ensure that a modern, strong protocol is used. All versions of SSL,
and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
above is strongly recommended. If no explicit
<code>ssl_version</code> is specified, the default
<code>PROTOCOL_TLS</code> is chosen. This protocol is insecure and
should not be used.
</p>
</recommendation>
<example>
<p>
The following code shows two different ways of setting up a connection
using SSL or TLS. They are both potentially insecure because the
default version is used.
</p>
<sample src="examples/insecure_default_protocol.py" />
<p>
Both of the cases above should be updated to use a secure protocol
instead, for instance by specifying
<code>ssl_version=PROTOCOL_TLSv1_1</code> as a keyword argument.
</p>
<p>
Note that <code>ssl.wrap_socket</code> has been deprecated in
Python 3.7. A preferred alternative is to use
<code>ssl.SSLContext</code>, which is supported in Python 2.7.9 and
3.2 and later versions.
</p>
</example>
<references>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"> Transport Layer Security</a>.</li>
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext"> class ssl.SSLContext</a>.</li>
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.wrap_socket"> ssl.wrap_socket</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,39 @@
/**
* @name Default version of SSL/TLS may be insecure
* @description Leaving the SSL/TLS version unspecified may result in an insecure
* default protocol being used.
* @id py/insecure-default-protocol
* @kind problem
* @problem.severity warning
* @precision high
* @tags security
* external/cwe/cwe-327
*/
import python
FunctionObject ssl_wrap_socket() {
result = any(ModuleObject ssl | ssl.getName() = "ssl").getAttribute("wrap_socket")
}
ClassObject ssl_Context_class() {
result = any(ModuleObject ssl | ssl.getName() = "ssl").getAttribute("SSLContext")
}
CallNode unsafe_call(string method_name) {
result = ssl_wrap_socket().getACall() and
method_name = "deprecated method ssl.wrap_socket"
or
result = ssl_Context_class().getACall() and
method_name = "ssl.SSLContext"
}
from CallNode call, string method_name
where
call = unsafe_call(method_name) and
not exists(call.getArgByName("ssl_version"))
select call, "Call to " + method_name + " does not specify a protocol, which may result in an insecure default being used."

View File

@@ -0,0 +1,50 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using a broken or weak cryptographic protocol may make a connection
vulnerable to interference from an attacker.
</p>
</overview>
<recommendation>
<p>
Ensure that a modern, strong protocol is used. All versions of SSL,
and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
above is strongly recommended.
</p>
</recommendation>
<example>
<p>
The following code shows a variety of ways of setting up a
connection using SSL or TLS. They are all insecure because of the
version specified.
</p>
<sample src="examples/insecure_protocol.py" />
<p>
All cases should be updated to use a secure protocol, such as
<code>PROTOCOL_TLSv1_1</code>.
</p>
<p>
Note that <code>ssl.wrap_socket</code> has been deprecated in
Python 3.7. A preferred alternative is to use
<code>ssl.SSLContext</code>, which is supported in Python 2.7.9 and
3.2 and later versions.
</p>
</example>
<references>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"> Transport Layer Security</a>.</li>
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext"> class ssl.SSLContext</a>.</li>
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.wrap_socket"> ssl.wrap_socket</a>.</li>
<li>pyOpenSSL documentation: <a href="https://pyopenssl.org/en/stable/api/ssl.html"> An interface to the SSL-specific parts of OpenSSL</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,93 @@
/**
* @name Use of insecure SSL/TLS version
* @description Using an insecure SSL/TLS version may leave the connection vulnerable to attacks.
* @id py/insecure-protocol
* @kind problem
* @problem.severity warning
* @precision high
* @tags security
* external/cwe/cwe-327
*/
import python
FunctionObject ssl_wrap_socket() {
result = the_ssl_module().getAttribute("wrap_socket")
}
ClassObject ssl_Context_class() {
result = the_ssl_module().getAttribute("SSLContext")
}
string insecure_version_name() {
// For `pyOpenSSL.SSL`
result = "SSLv2_METHOD" or
result = "SSLv23_METHOD" or
result = "SSLv3_METHOD" or
result = "TLSv1_METHOD" or
// For the `ssl` module
result = "PROTOCOL_SSLv2" or
result = "PROTOCOL_SSLv3" or
result = "PROTOCOL_SSLv23" or
result = "PROTOCOL_TLS" or
result = "PROTOCOL_TLSv1"
}
private ModuleObject the_ssl_module() {
result = any(ModuleObject m | m.getName() = "ssl")
}
private ModuleObject the_pyOpenSSL_module() {
result = any(ModuleObject m | m.getName() = "pyOpenSSL.SSL")
}
/* A syntactic check for cases where points-to analysis cannot infer the presence of
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
* library.
*/
predicate probable_insecure_ssl_constant(CallNode call, string insecure_version) {
exists(ControlFlowNode arg | arg = call.getArgByName("ssl_version") |
arg.(AttrNode).getObject(insecure_version).refersTo(the_ssl_module())
or
arg.(NameNode).getId() = insecure_version and
exists(Import imp |
imp.getAnImportedModuleName() = "ssl" and
imp.getAName().getAsname().(Name).getId() = insecure_version
)
)
}
predicate unsafe_ssl_wrap_socket_call(CallNode call, string method_name, string insecure_version) {
(
call = ssl_wrap_socket().getACall() and
method_name = "deprecated method ssl.wrap_socket"
or
call = ssl_Context_class().getACall() and
method_name = "ssl.SSLContext"
)
and
insecure_version = insecure_version_name()
and
(
call.getArgByName("ssl_version").refersTo(the_ssl_module().getAttribute(insecure_version))
or
probable_insecure_ssl_constant(call, insecure_version)
)
}
ClassObject the_pyOpenSSL_Context_class() {
result = any(ModuleObject m | m.getName() = "pyOpenSSL.SSL").getAttribute("Context")
}
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
call = the_pyOpenSSL_Context_class().getACall() and
insecure_version = insecure_version_name() and
call.getArg(0).refersTo(the_pyOpenSSL_module().getAttribute(insecure_version))
}
from CallNode call, string method_name, string insecure_version
where
unsafe_ssl_wrap_socket_call(call, method_name, insecure_version)
or
unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
select call, "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name + "."

View File

@@ -0,0 +1,8 @@
import ssl
import socket
# Using the deprecated ssl.wrap_socket method
ssl.wrap_socket(socket.socket())
# Using SSLContext
context = ssl.SSLContext()

View File

@@ -0,0 +1,16 @@
import ssl
import socket
# Using the deprecated ssl.wrap_socket method
ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_SSLv2)
# Using SSLContext
context = ssl.SSLContext(ssl_version=ssl.PROTOCOL_SSLv3)
# Using pyOpenSSL
from pyOpenSSL import SSL
context = SSL.Context(SSL.TLSv1_METHOD)

View File

@@ -0,0 +1,2 @@
| 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. |
| 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. |

View File

@@ -0,0 +1 @@
Security/CWE-327/InsecureDefaultProtocol.ql

View File

@@ -0,0 +1,13 @@
| 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. |
| 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. |
| 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. |
| InsecureProtocol.py:10:1:10:42 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |
| InsecureProtocol.py:11:1:11:42 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv3 specified in call to ssl.SSLContext. |
| InsecureProtocol.py:12:1:12:42 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_TLSv1 specified in call to ssl.SSLContext. |
| InsecureProtocol.py:14:1:14:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
| InsecureProtocol.py:15:1:15:30 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv23_METHOD specified in call to pyOpenSSL.SSL.Context. |
| InsecureProtocol.py:16:1:16:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv3_METHOD specified in call to pyOpenSSL.SSL.Context. |
| InsecureProtocol.py:17:1:17:29 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version TLSv1_METHOD specified in call to pyOpenSSL.SSL.Context. |
| InsecureProtocol.py:32:1:32:19 | ControlFlowNode for Attribute() | Insecure SSL/TLS protocol version SSLv2_METHOD specified in call to pyOpenSSL.SSL.Context. |
| 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. |
| InsecureProtocol.py:49:1:49:38 | ControlFlowNode for SSLContext() | Insecure SSL/TLS protocol version PROTOCOL_SSLv2 specified in call to ssl.SSLContext. |

View File

@@ -0,0 +1,50 @@
import ssl
from pyOpenSSL import SSL
from ssl import SSLContext
# true positives
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2)
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3)
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1)
SSLContext(ssl_version=ssl.PROTOCOL_SSLv2)
SSLContext(ssl_version=ssl.PROTOCOL_SSLv3)
SSLContext(ssl_version=ssl.PROTOCOL_TLSv1)
SSL.Context(SSL.SSLv2_METHOD)
SSL.Context(SSL.SSLv23_METHOD)
SSL.Context(SSL.SSLv3_METHOD)
SSL.Context(SSL.TLSv1_METHOD)
# not relevant
wrap_socket(ssl_version=ssl.PROTOCOL_SSLv3)
wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1)
wrap_socket(ssl_version=ssl.PROTOCOL_SSLv2)
Context(SSL.SSLv3_METHOD)
Context(SSL.TLSv1_METHOD)
Context(SSL.SSLv2_METHOD)
Context(SSL.SSLv23_METHOD)
# true positive using flow
METHOD = SSL.SSLv2_METHOD
SSL.Context(METHOD)
# secure versions
ssl.wrap_socket(ssl_version=ssl.PROTOCOL_TLSv1_1)
SSLContext(ssl_version=ssl.PROTOCOL_TLSv1_1)
SSL.Context(SSL.TLSv1_1_METHOD)
# possibly insecure default
ssl.wrap_socket()
context = SSLContext()
# importing the protocol constant directly
from ssl import PROTOCOL_SSLv2
ssl.wrap_socket(ssl_version=PROTOCOL_SSLv2)
SSLContext(ssl_version=PROTOCOL_SSLv2)

View File

@@ -0,0 +1 @@
Security/CWE-327/InsecureProtocol.ql

View File

@@ -1 +1,2 @@
semmle-extractor-options: -p ../lib/ --max-import-depth=3
optimize: true

View File

@@ -0,0 +1,10 @@
SSLv2_METHOD = 1
SSLv3_METHOD = 2
SSLv23_METHOD = 3
TLSv1_METHOD = 4
TLSv1_1_METHOD = 5
TLSv1_2_METHOD = 6
class Context(object):
def __init__(self, *args, **kwargs):
pass