From ef82617b422994fa109683bcb9d341fa07eab897 Mon Sep 17 00:00:00 2001 From: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> Date: Sat, 20 Jun 2026 23:23:37 -0700 Subject: [PATCH] Python: fix py/insecure-protocol false positive on ssl.create_default_context() Since Python 3.10, `ssl.create_default_context()` returns a context whose `minimum_version` defaults to `TLSVersion.TLSv1_2`, so TLS 1.0 and TLS 1.1 are not allowed. The model previously encoded the pre-3.10 behavior and flagged these versions as allowed, producing false positives. Update `SslDefaultContextCreation` to allow only TLSv1_2 and TLSv1_3, refresh the test expectations, and add a regression test for the common safe case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com> --- python/ql/src/Security/CWE-327/Ssl.qll | 7 +++++-- ...-06-20-insecure-protocol-create-default-context.md | 4 ++++ .../InsecureProtocol.expected | 2 -- .../Security/CWE-327-InsecureProtocol/ssl_fluent.py | 11 +++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 python/ql/src/change-notes/2026-06-20-insecure-protocol-create-default-context.md diff --git a/python/ql/src/Security/CWE-327/Ssl.qll b/python/ql/src/Security/CWE-327/Ssl.qll index c3fd0366436d..5b3d89d8b307 100644 --- a/python/ql/src/Security/CWE-327/Ssl.qll +++ b/python/ql/src/Security/CWE-327/Ssl.qll @@ -33,9 +33,12 @@ class SslDefaultContextCreation extends ContextCreation { this = API::moduleImport("ssl").getMember("create_default_context").getACall() } - // Allowed insecure versions are "TLSv1" and "TLSv1_1" + // Since Python 3.10, `ssl.create_default_context` returns a context whose + // `minimum_version` defaults to `TLSVersion.TLSv1_2`, so TLSv1 and TLSv1_1 are not allowed. + // (Earlier Python versions also allowed TLSv1 and TLSv1_1.) // see https://docs.python.org/3/library/ssl.html#context-creation - override ProtocolVersion getProtocol() { result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] } + // and https://docs.python.org/3/whatsnew/3.10.html#ssl + override ProtocolVersion getProtocol() { result in ["TLSv1_2", "TLSv1_3"] } } /** Gets a reference to an `ssl.Context` instance. */ diff --git a/python/ql/src/change-notes/2026-06-20-insecure-protocol-create-default-context.md b/python/ql/src/change-notes/2026-06-20-insecure-protocol-create-default-context.md new file mode 100644 index 000000000000..9841156a5635 --- /dev/null +++ b/python/ql/src/change-notes/2026-06-20-insecure-protocol-create-default-context.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `py/insecure-protocol` query no longer reports `ssl.create_default_context` as allowing TLS 1.0 or TLS 1.1. Since Python 3.10, the context returned by `ssl.create_default_context` has its `minimum_version` set to `TLSVersion.TLSv1_2` by default, so these protocol versions are not allowed and the previous alerts were false positives. diff --git a/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/InsecureProtocol.expected b/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/InsecureProtocol.expected index 491a23823642..099f38610b8a 100644 --- a/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/InsecureProtocol.expected +++ b/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/InsecureProtocol.expected @@ -41,5 +41,3 @@ | ssl_fluent.py:97:14:97:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@. | ssl_fluent.py:65:15:65:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext | | ssl_fluent.py:146:14:146:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@. | ssl_fluent.py:142:15:142:46 | ControlFlowNode for Attribute() | call to ssl.SSLContext | | ssl_fluent.py:165:14:165:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version SSLv3 allowed by $@. | ssl_fluent.py:161:15:161:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context | -| ssl_fluent.py:165:14:165:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1 allowed by $@. | ssl_fluent.py:161:15:161:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context | -| ssl_fluent.py:165:14:165:20 | ControlFlowNode for context | Insecure SSL/TLS protocol version TLSv1_1 allowed by $@. | ssl_fluent.py:161:15:161:65 | ControlFlowNode for Attribute() | call to ssl.create_default_context | diff --git a/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/ssl_fluent.py b/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/ssl_fluent.py index e4d71de56955..f43cf98cd375 100644 --- a/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/ssl_fluent.py +++ b/python/ql/test/query-tests/Security/CWE-327-InsecureProtocol/ssl_fluent.py @@ -164,3 +164,14 @@ def test_fluent_explicitly_unsafe(): with socket.create_connection((hostname, 443)) as sock: with context.wrap_socket(sock, server_hostname=hostname) as ssock: # $ Alert print(ssock.version()) + +# Since Python 3.10, `ssl.create_default_context` sets `minimum_version` to +# `TLSVersion.TLSv1_2`, so TLSv1 and TLSv1_1 are not allowed. +# see https://docs.python.org/3/library/ssl.html#context-creation +def test_fluent_default_context_safe(): + hostname = 'www.python.org' + context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) + + with socket.create_connection((hostname, 443)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: # No alert + print(ssock.version())