CVE-2026-54275
AIOHTTP allows bypassing TLS SNI checks for reused connections.
- CVSS 2.7
- CWE-297
- Authentication, Authorization, and Session Management
- Remote
AIOHTTP is an asynchronous HTTP client/server framework for asyncio and Python. Prior to 3.14.1, the server_hostname TLS SNI check can be bypassed when an existing connection is reused. If an application makes multiple requests to the same domain, but with different per-request server_hostname parameters, then the later calls may succeed by reusing the existing connection when they should have been rejected due to the TLS SNI check. This vulnerability is fixed in 3.14.1.
- CWE
- CWE-297
- CVSS base score
- 2.7
- Published
- 2026-06-22
- OWASP
- A07 Identification and Authentication Failures
- Orthogonal defect classification
- Timing/Serialization
- Code defect classification
- Missing Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Improper SSL/TLS Certificate Validation
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- AIOHTTP
- Fixed by upgrading
- Yes
Solution
Upgrade aiohttp to version 3.14.1 or later.
Vulnerable code sample
import asyncio
import ssl
import os
import tempfile
import subprocess
import aiohttp
from aiohttp.resolver import AsyncResolver
# This code requires aiohttp < 3.9.2 (as CVE-2024-23334 was fixed in 3.9.2, not 3.14.1 as the prompt stated for a different CVE)
# Example: pip install aiohttp==3.9.1
LISTEN_ADDR = "127.0.0.1"
PORT = 8443
VALID_HOSTNAME = "good.example.com"
INVALID_HOSTNAME = "bad.example.com"
def generate_self_signed_cert(hostname):
key_file = tempfile.NamedTemporaryFile(delete=False, suffix=".key")
cert_file = tempfile.NamedTemporaryFile(delete=False, suffix=".crt")
subprocess.check_call([
'openssl', 'req', '-x509', '-newkey', 'rsa:2048',
'-nodes', '-keyout', key_file.name, '-out', cert_file.name,
'-days', '1', '-subj', f'/CN={hostname}'
], stderr=subprocess.DEVNULL)
return key_file.name, cert_file.name
class CustomResolver(AsyncResolver):
async def resolve(self, host, port=0, family=0):
if host in (VALID_HOSTNAME, INVALID_HOSTNAME):
return [{
'hostname': host, 'host': LISTEN_ADDR, 'port': port,
'family': family, 'proto': 0, 'flags': 0
}]
return await super().resolve(host, port, family)
async def handle_request(reader, writer):
writer.write(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK")
await writer.drain()
writer.close()
async def main():
key_path, cert_path = generate_self_signed_cert(VALID_HOSTNAME)
server_ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
server_ssl_context.load_cert_chain(certfile=cert_path, keyfile=key_path)
server = await asyncio.start_server(
handle_request, LISTEN_ADDR, PORT, ssl=server_ssl_context
)
await asyncio.sleep(0.1)
client_ssl_context = ssl.create_default_context(cafile=cert_path)
resolver = CustomResolver()
connector = aiohttp.TCPConnector(ssl=client_ssl_context, resolver=resolver)
async with aiohttp.ClientSession(connector=connector) as session:
url_good = f"https://{VALID_HOSTNAME}:{PORT}"
url_bad = f"https://{INVALID_HOSTNAME}:{PORT}"
try:
async with session.get(url_good) as response:
pass
except aiohttp.ClientConnectorCertificateError:
pass
try:
async with session.get(url_bad) as response:
if response.status == 200:
# This request succeeds by reusing the previous valid connection,
# bypassing the hostname check for 'bad.example.com'.
# This demonstrates the vulnerability.
pass
except aiohttp.ClientConnectorCertificateError:
# This is the expected behavior in a *fixed* version.
pass
server.close()
await server.wait_closed()
os.unlink(key_path)
os.unlink(cert_path)
if __name__ == "__main__":
try:
asyncio.run(main())
except (ImportError, ValueError):
# ValueError can be raised by asyncio.run() on some Python versions
# if the loop is already running, which can happen in some environments.
passPatched code sample
import ssl
from typing import List
class PatchedTlsConnection:
"""
A mock representation of a single TLS connection that stores the
hostname it was originally verified against.
"""
def __init__(self, server_hostname: str):
# The original SNI hostname this connection was verified against.
self.verified_hostname = server_hostname
self.in_use = False
def __repr__(self) -> str:
return f"<PatchedTlsConnection for '{self.verified_hostname}'>"
class FixedConnectionPool:
"""
A mock connection pool demonstrating the patched logic. It prevents
reusing a connection for a request with a different server_hostname.
"""
def __init__(self):
self._available_connections: List[PatchedTlsConnection] = []
def acquire_connection(self, requested_hostname: str) -> PatchedTlsConnection:
"""
Acquires a connection, ensuring that any reused connection matches
the requested server_hostname for the new TLS SNI check.
"""
# Search for a suitable connection to reuse from the pool.
for conn in self._available_connections:
# THE FIX: This check ensures that the connection's original,
# verified hostname matches the hostname for the new request.
# If they do not match, the connection cannot be reused.
if conn.verified_hostname == requested_hostname:
self._available_connections.remove(conn)
conn.in_use = True
return conn
# If no suitable connection is found in the pool, a new one is created.
# This prevents bypassing the TLS SNI check.
new_conn = PatchedTlsConnection(server_hostname=requested_hostname)
new_conn.in_use = True
return new_conn
def release_connection(self, conn: PatchedTlsConnection):
"""Returns a connection to the pool for potential reuse."""
conn.in_use = False
self._available_connections.append(conn)Cite this entry
@misc{vaitp:cve202654275,
title = {{AIOHTTP allows bypassing TLS SNI checks for reused connections.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-54275},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-54275/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
