This is a fake shipping label that only works when the front desk and the loading dock read the box differently
CVE-2025-55315 is an HTTP request smuggling flaw in ASP.NET Core's Kestrel parser, specifically around malformed chunk extensions in HTTP/1.x requests. Microsoft lists affected supported lines as ASP.NET Core 8.0.0 through 8.0.20, 9.0.0 through 9.0.9, 10.0.0-rc.1.25451.107 and earlier, plus Microsoft.AspNetCore.Server.Kestrel.Core 2.3.0 and earlier for ASP.NET Core 2.x; self-contained deployments must also be rebuilt after updating the runtime.
Microsoft's 9.9 score captures the nastiest possible application outcome, but that is not the common enterprise reality. The real risk is gated by multiple frictions: Microsoft's own vector requires PR:L, request smuggling only matters when a front end and Kestrel disagree on request boundaries, HTTP/2 and HTTP/3 are out of scope, and many estates terminate through proxies or managed platforms that normalize or block malformed requests before Kestrel sees them.
4 steps from start to impact.
Get a foothold with low privilege
PR:L scoring. This is already post-initial-access for most enterprise apps, which sharply narrows who can exercise the bug at scale.- A reachable ASP.NET Core app using vulnerable Kestrel code
- Attacker has valid low-privilege access or equivalent authorized request path
- Target accepts HTTP/1.1 traffic
- Unauthenticated internet spray does not satisfy the vendor's own privilege requirement
- MFA, SSO, API gateway auth, and tenant enrollment reduce the reachable population
Send a malformed chunked request
sirredbeard/CVE-2025-55315-repro or the public gist PoC, the attacker sends an HTTP/1.1 request with crafted Transfer-Encoding: chunked data and an invalid chunk extension/newline sequence. Vulnerable Kestrel versions can parse the boundary differently than an upstream component, creating a smuggled second request opportunity.- HTTP/1.1 with chunked request bodies is allowed end-to-end
- Malformed chunk extension bytes survive the path to Kestrel
- HTTP/2 and HTTP/3 are not affected because they do not use chunked transfer encoding
- WAFs, CDNs, reverse proxies, and load balancers may reject or normalize malformed chunk framing before it reaches Kestrel
Exploit a parser mismatch at the security boundary
- A front-end security or routing layer sits in front of Kestrel, or the app itself manually reads/forwards request streams
- The front end and back end interpret the malformed request differently
- Single-hop apps where Kestrel itself performs authn/authz have less leverage for the attacker
- Many deployments do not put trust decisions solely in front-end headers or front-door routing
400/timeout anomalies, malformed chunk parsing errors, and request pairs sharing a connection where front-end and app logs disagree on counts or paths.Cash out through app-specific abuse
- The application or front end makes security decisions that can be bypassed by a smuggled request
- Useful target endpoints exist behind the trust boundary
- If origin-side authorization is strong, the smuggled request often still runs under the same low-privilege context
- No public KEV listing or reviewed campaign evidence suggests broad, reliable operationalization yet
The supporting signals.
| In-the-wild status | No public exploitation evidence found in reviewed sources. Microsoft said scenarios are configuration-dependent, Andrew Lock noted there was no announced in-the-wild evidence, and CISA KEV does not list this CVE. |
|---|---|
| Proof-of-concept availability | Public PoCs exist. There is a public repro repo (sirredbeard/CVE-2025-55315-repro) and a public gist showing a chunked-smuggling payload, so defenders should assume competent operators can reproduce the bug. |
| EPSS | 0.01681 (~1.68%) from the provided intel. Public mirrors report a roughly 79.7 percentile, which says "more likely than most CVEs" but still nowhere near the operational urgency of a hot KEV bug. |
| KEV status | Not KEV-listed. No CISA KEV entry was found for CVE-2025-55315 in the catalog review. |
| CVSS vector reality check | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L overstates fleet urgency. The decisive part is PR:L: this is not anonymous internet-to-root; it assumes the attacker already has some valid position. |
| Affected versions | Supported affected lines: ASP.NET Core 8.0.0-8.0.20, 9.0.0-9.0.9, 10.0.0-rc.1.25451.107 and earlier, plus Microsoft.AspNetCore.Server.Kestrel.Core <= 2.3.0 for 2.x projects. Public research also indicates older unsupported .NET/ASP.NET Core lines can still exhibit the issue. |
| Fixed versions | Vendor fixes: ASP.NET Core 8.0.21, 9.0.10, 10.0.0-rc.2.25502.107/10.0.100-rc.2.25476.107 family depending on package path, and Microsoft.AspNetCore.Server.Kestrel.Core 2.3.6. Self-contained apps must be rebuilt and redeployed after updating. |
| Exposure reality | This is an HTTP/1.x smuggling problem, not a universal ASP.NET compromise. HTTP/2 and HTTP/3 are out of scope, and Microsoft/Azure note that patched front-end layers can block malicious requests before they hit the app. Azure App Service stated its front ends were patched by 2025-10-20 to mitigate tenant apps even when app runtimes lagged. |
| Scanner / detection coverage | Version-based detection is straightforward; exploit-path validation is harder. You can reliably inventory vulnerable runtime versions, but proving real exploitability requires raw-request testing against the exact proxy/app chain. Expect false confidence if you only check package versions and ignore the front-door architecture. |
| Researcher / disclosure | Disclosed 2025-10-14; researcher credit points to Siddhant Kalgutkar of Praetorian. Praetorian's write-up says the bug was found in malformed chunked transfer extension parsing in Kestrel. |
noisgate verdict.
The biggest downgrading factor is attacker position: Microsoft's own vector says PR:L, which makes this a post-auth or already-authorized abuse path rather than commodity unauthenticated edge exploitation. It still lands in HIGH because Kestrel is widely deployed, public PoCs exist, and when the right proxy/security-boundary mismatch is present the blast radius can jump from one request parser bug to real authz bypass outcomes.
Why this verdict
- Vendor 9.9 starts too high for fleet triage because
PR:Lmatters. Requiring a valid low-privilege position means the attacker is already past your internet front door, which is a major downward adjustment from a true anonymous edge bug. - Exploitability is architecture-dependent. Request smuggling only pays off when a front end and Kestrel disagree on request boundaries, or when app code manually handles raw streams in unsafe ways. That narrows the exposed population versus a bug that compromises every reachable app equally.
- Threat intel is not screaming. Public PoCs exist, but there is no KEV listing, no reviewed public campaign evidence, and the provided EPSS is low in absolute terms. That keeps this out of
CRITICALeven though the worst-case impact is ugly.
Why not higher?
It is not CRITICAL because this is not a universal one-packet internet compromise. The chain assumes low privileges or an authorized path, HTTP/1.x behavior, and a meaningful parser mismatch at a security boundary; miss any of those and the exploit collapses into a bad request, timeout, or same-context action.
Why not lower?
It is not MEDIUM because the vulnerable component is common, the exploit technique is well understood, and public repro tooling already exists. In the subset of deployments that put trust decisions in front of Kestrel, a parser mismatch can directly undermine authentication, authorization, or route isolation.
What to do — in priority order.
- Force HTTP/2 or HTTP/3 where feasible — Disable or sharply limit external HTTP/1.1 on internet-facing ASP.NET Core endpoints if your clients can tolerate it. This directly removes the vulnerable chunked-transfer attack surface and should be deployed within 30 days for
HIGHfindings. - Normalize and reject malformed chunked requests at the edge — Use your reverse proxy, CDN, WAF, or API gateway to reject malformed
Transfer-Encoding: chunkedtraffic and enforce strict request parsing before Kestrel sees it. Prioritize internet-facing apps and deploy within 30 days. - Move authz decisions to the origin where possible — Do not rely solely on front-door routing or header trust for protected actions; enforce authorization in the ASP.NET Core app or downstream service as well. This reduces the payoff of a smuggled request and should be implemented within 30 days on high-value apps.
- Prioritize raw-body and proxying code paths for review — Apps that manually read, manipulate, or forward request streams are the most dangerous places for this bug to turn into a real bypass. Review those services first and apply compensating restrictions within 30 days.
- Correlate front-end and origin logs — Build detections for request-count mismatches, unexpected protected-route hits, malformed chunk parsing errors, and connection reuse anomalies between proxy and Kestrel logs. Stand this up within 30 days for externally reachable estates.
- MFA alone does not mitigate an already-authenticated smuggling path; it only helps before the attacker gets a valid session or token.
- Network segmentation by itself does not solve parser disagreement if the vulnerable path is your normal front-door proxy chain.
- Package inventory without runtime inventory misses self-contained deployments and host-level ASP.NET Core runtimes.
- Assuming all reverse proxies save you is unsafe; some front ends normalize or block this traffic, others may still pass exploitable bytes depending on configuration and version.
Crowdsourced verification payload.
Run this on the target host/container to check installed ASP.NET Core runtimes, or in a CI/build workspace to also scan project files for Microsoft.AspNetCore.Server.Kestrel.Core. Invoke it as python3 check_cve_2025_55315.py /path/to/app or python3 check_cve_2025_55315.py for runtime-only checks; no admin rights are required, but the account must be able to run dotnet --list-runtimes and read the app directory.
#!/usr/bin/env python3
# check_cve_2025_55315.py
# Detect likely exposure to CVE-2025-55315 on a host or in a source tree.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import json
import os
import re
import subprocess
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
CVE = "CVE-2025-55315"
def parse_version_tuple(v):
# Handles numeric versions like 8.0.21 and 9.0.10
m = re.match(r"^(\d+)\.(\d+)\.(\d+)$", v)
if not m:
return None
return tuple(int(x) for x in m.groups())
def compare_numeric(v1, v2):
t1 = parse_version_tuple(v1)
t2 = parse_version_tuple(v2)
if t1 is None or t2 is None:
return None
return (t1 > t2) - (t1 < t2)
def version_in_range_numeric(v, low, high_inclusive):
c1 = compare_numeric(v, low)
c2 = compare_numeric(v, high_inclusive)
return c1 is not None and c2 is not None and c1 >= 0 and c2 <= 0
def is_vulnerable_runtime(ver):
# Microsoft-supported lines from the advisory
if version_in_range_numeric(ver, "8.0.0", "8.0.20"):
return True
if version_in_range_numeric(ver, "9.0.0", "9.0.9"):
return True
# 10 RC handling: anything explicitly on rc.1 is vulnerable
if ver.startswith("10.0.0-rc.1"):
return True
return False
def is_patched_runtime(ver):
if compare_numeric(ver, "8.0.21") is not None and compare_numeric(ver, "8.0.21") >= 0 and ver.startswith("8."):
return True
if compare_numeric(ver, "9.0.10") is not None and compare_numeric(ver, "9.0.10") >= 0 and ver.startswith("9."):
return True
if ver.startswith("10.0.0-rc.2") or ver.startswith("10.0.0") or ver.startswith("10.1"):
return True
return False
def parse_dotnet_runtimes():
try:
proc = subprocess.run(
["dotnet", "--list-runtimes"],
capture_output=True,
text=True,
check=False,
timeout=15,
)
except Exception:
return None, ["dotnet CLI not available"]
if proc.returncode != 0:
return None, [f"dotnet returned {proc.returncode}: {proc.stderr.strip()}"]
runtimes = []
for line in proc.stdout.splitlines():
# Example: Microsoft.AspNetCore.App 8.0.20 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
m = re.match(r"^(Microsoft\.AspNetCore\.App)\s+([^\s]+)\s+\[.*\]$", line.strip())
if m:
runtimes.append({"name": m.group(1), "version": m.group(2)})
return runtimes, []
def scan_csproj_for_kestrel(path):
findings = []
for file in path.rglob("*.csproj"):
try:
tree = ET.parse(file)
root = tree.getroot()
for elem in root.iter():
tag = elem.tag.split('}')[-1]
if tag == "PackageReference":
include = elem.attrib.get("Include") or elem.attrib.get("Update") or ""
version = elem.attrib.get("Version")
if not version:
for child in elem:
ctag = child.tag.split('}')[-1]
if ctag == "Version" and child.text:
version = child.text.strip()
if include == "Microsoft.AspNetCore.Server.Kestrel.Core" and version:
findings.append((str(file), version))
except Exception:
pass
return findings
def normalize_version(s):
# trim common NuGet range wrappers when possible
s = s.strip()
s = s.strip("[]()")
s = s.split(",")[0].strip()
return s
def assess_kestrel_pkg(ver):
v = normalize_version(ver)
cmp = compare_numeric(v, "2.3.0")
if cmp is not None and cmp <= 0:
return "vulnerable"
cmp2 = compare_numeric(v, "2.3.6")
if cmp2 is not None and cmp2 >= 0:
return "patched"
return "unknown"
def main():
app_path = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else None
notes = []
vulnerable = []
patched = []
unknown = []
runtimes, errs = parse_dotnet_runtimes()
notes.extend(errs)
if runtimes is None:
unknown.append("Unable to inventory installed ASP.NET Core runtimes")
else:
asp = [r for r in runtimes if r["name"] == "Microsoft.AspNetCore.App"]
if not asp:
notes.append("No Microsoft.AspNetCore.App runtimes found via dotnet CLI")
for r in asp:
ver = r["version"]
if is_vulnerable_runtime(ver):
vulnerable.append(f"Runtime {ver} is in Microsoft affected range")
elif is_patched_runtime(ver):
patched.append(f"Runtime {ver} is at or above fixed level for its train")
elif ver.startswith(("6.", "7.", "5.", "3.", "2.")):
unknown.append(f"Runtime {ver} is unsupported/not covered by current Microsoft supported-range check")
else:
unknown.append(f"Runtime {ver} could not be classified")
if app_path and app_path.exists() and app_path.is_dir():
refs = scan_csproj_for_kestrel(app_path)
if refs:
for file, ver in refs:
state = assess_kestrel_pkg(ver)
if state == "vulnerable":
vulnerable.append(f"{file}: Microsoft.AspNetCore.Server.Kestrel.Core {ver} <= 2.3.0")
elif state == "patched":
patched.append(f"{file}: Microsoft.AspNetCore.Server.Kestrel.Core {ver} >= 2.3.6")
else:
unknown.append(f"{file}: could not classify Kestrel.Core version {ver}")
else:
notes.append("No csproj PackageReference to Microsoft.AspNetCore.Server.Kestrel.Core found")
elif app_path:
unknown.append(f"Path not found or not a directory: {app_path}")
result = {
"cve": CVE,
"vulnerable_findings": vulnerable,
"patched_findings": patched,
"unknown_findings": unknown,
"notes": notes,
}
print(json.dumps(result, indent=2))
if vulnerable:
print("VULNERABLE")
sys.exit(1)
if patched and not unknown:
print("PATCHED")
sys.exit(0)
print("UNKNOWN")
sys.exit(2)
if __name__ == "__main__":
main()
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.