← Back to Feed CACHED · 2026-05-17 09:42:19 · cache_key CVE-2025-29912
CVE-2025-55315 · CWE-444 · Disclosed 2025-10-14

Inconsistent interpretation of http requests

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
01 · The Real Story

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.

"Serious, but not a fleet-wide fire: this is a post-auth, deployment-specific smuggling bug, not generic internet RCE."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Get a foothold with low privilege

The attacker first needs some form of valid low-privilege access that can reach the target application: a user session, API key, tenant account, or other authorized position consistent with Microsoft's PR:L scoring. This is already post-initial-access for most enterprise apps, which sharply narrows who can exercise the bug at scale.
Conditions required:
  • 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
Where this breaks in practice:
  • Unauthenticated internet spray does not satisfy the vendor's own privilege requirement
  • MFA, SSO, API gateway auth, and tenant enrollment reduce the reachable population
Detection/coverage: IAM, API gateway, and auth logs can usually show the account or token used to reach this stage; vuln scanners cannot prove this prerequisite from version data alone.
STEP 02

Send a malformed chunked request

Using a raw socket client or public repro tooling such as 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.
Conditions required:
  • HTTP/1.1 with chunked request bodies is allowed end-to-end
  • Malformed chunk extension bytes survive the path to Kestrel
Where this breaks in practice:
  • 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
Detection/coverage: Version scanners can flag affected runtimes; active detection requires raw-request testing and can be noisy. An unofficial Nuclei template request exists, but coverage is not authoritative.
STEP 03

Exploit a parser mismatch at the security boundary

The exploit becomes valuable only if a front-end control and Kestrel disagree about where the first request ends. That is the classic request-smuggling condition: the front end treats the payload as one request while Kestrel processes a hidden follow-on request that can bypass front-end routing, auth, or inspection assumptions.
Conditions required:
  • 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
Where this breaks in practice:
  • 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
Detection/coverage: Look for 400/timeout anomalies, malformed chunk parsing errors, and request pairs sharing a connection where front-end and app logs disagree on counts or paths.
STEP 04

Cash out through app-specific abuse

Impact depends on application design, not just parser behavior. In the best attacker case this can bypass a front-end security feature and reach protected routes, poison cache behavior, replay state-changing requests, or manipulate downstream trust boundaries; in the average case it produces a rejected request, timeout, or a same-privilege action that does not meaningfully expand access.
Conditions required:
  • The application or front end makes security decisions that can be bypassed by a smuggled request
  • Useful target endpoints exist behind the trust boundary
Where this breaks in practice:
  • 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
Detection/coverage: High-fidelity detection comes from correlating reverse-proxy logs with Kestrel/app logs for request-count mismatches, unexpected protected-route hits, and reuse of a single TCP connection across divergent paths.
03 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo 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 availabilityPublic 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.
EPSS0.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 statusNot KEV-listed. No CISA KEV entry was found for CVE-2025-55315 in the catalog review.
CVSS vector reality checkCVSS: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 versionsSupported 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 versionsVendor 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 realityThis 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 coverageVersion-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 / disclosureDisclosed 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.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to HIGH (7.6/10)

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.

HIGH Affected version ranges and fixed versions
MEDIUM Real-world exploitability across mixed proxy architectures
MEDIUM Assessment that vendor CVSS overstates average enterprise urgency

Why this verdict

  • Vendor 9.9 starts too high for fleet triage because PR:L matters. 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 CRITICAL even 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.

05 · Compensating Control

What to do — in priority order.

  1. 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 HIGH findings.
  2. Normalize and reject malformed chunked requests at the edge — Use your reverse proxy, CDN, WAF, or API gateway to reject malformed Transfer-Encoding: chunked traffic and enforce strict request parsing before Kestrel sees it. Prioritize internet-facing apps and deploy within 30 days.
  3. 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.
  4. 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.
  5. 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.
What doesn't work
  • 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.
06 · Verification

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.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/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()
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning, treat this as a HIGH patching problem for internet-facing ASP.NET Core apps that still allow HTTP/1.1 and sit behind front-end auth, WAF, gateway, or proxy layers. Apply edge-side compensating controls within the noisgate mitigation SLA of 30 days—or faster for your exposed crown-jewel apps—then patch supported runtimes, rebuild self-contained deployments, and close out the fleet within the noisgate remediation SLA of 180 days; if you discover unsupported .NET 6/7-era workloads, move them into exception handling immediately because you may have exposure without a vendor-serviced fix path.

Sources

  1. NVD CVE record
  2. GitHub Security Advisory GHSA-5rrx-jjjq-q2r5
  3. Microsoft advisory announcement issue #371
  4. MSRC blog: Understanding CVE-2025-55315
  5. Andrew Lock technical analysis
  6. Praetorian researcher write-up
  7. CISA Known Exploited Vulnerabilities catalog
  8. Azure App Service mitigation note
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.