Two small benches that feed byte-identical HTTP input to several independent parser implementations and print, side by side, what each one understood.
Everything runs on 127.0.0.1. Nothing here attacks anything: the targets are
local reflector programs written for this repository, and the output is a
table, not an exploit.
Squarcina, Adão, Veronese and Maffei, Cookie Crumbles: Breaking and Fixing Web Session Integrity (32nd USENIX Security Symposium, 2023), surveyed cookie handling across browsers and 13 web frameworks. On the server-side parsers they wrote:
We acknowledge that this approach does not constitute a systematic evaluation of server-side parsing inconsistencies. However, our initial analysis provided strong evidence of the pervasiveness of the issue. We leave such comprehensive study as future work.
This is a start on that future work, plus the same treatment for
multipart/form-data, which has no equivalent public bench at all.
The point is not that any one parser is wrong. It is that when two parts of a system disagree about what a request says, the disagreement itself is the bug class. A gateway that inspects a field and an application that reads a different value for it are both behaving exactly as written.
ASP.NET Core (RequestCookieCollection), Go (net/http), PHP (native
$_COOKIE), the algorithm of the npm cookie package that Express and Koa
use, and Python (http.cookies).
Covers duplicate names, case, percent-decoding, quoting, separators,
whitespace, control and high bytes, and repeated Cookie headers.
ASP.NET Core (ReadFormAsync), Go (mime/multipart), PHP, and Python
(email with an HTTP policy).
Covers duplicate field names, quoting of the name parameter, repeated and
malformed Content-Disposition, bare LF line endings, boundary handling,
RFC 2231 encoded names, preamble and epilogue, and x-www-form-urlencoded.
Needs the .NET SDK, Go, PHP, Node and Python 3 on the path.
bash cookie-header/run.sh
bash multipart-form/run.sh
Each script builds the reflectors, starts them on loopback, runs the vectors, prints the matrix and stops the servers.
The scripts flag vectors where one implementation stands alone. That flag is a pointer, not a verdict. Three things worth keeping in mind, all of which bit me while building this:
- Standing alone is not the same as being wrong. In the multipart bench,
three of the cases where ASP.NET Core differs are cases where it is
stricter than the other three: it rejects a duplicated
Content-Disposition, bare LF line endings, and a fake boundary inside a value, where Go, PHP and Python accept them. - Make the reflectors report the same shape. An early version of the multipart reflector printed the file size on one implementation and not the others, which made an identical result look like a divergence. The comparison is only as good as the weakest reflector.
- Check the direction. A parser that drops a field fails closed; one that invents or merges a field does not. They are not the same finding.
results/ holds raw output. Summarised:
| bench | alone | of | notes |
|---|---|---|---|
| cookie header | 6 | 39 | case-insensitive name matching, whitespace in names, and a comma in a value discarding the rest of the header |
| multipart | 7 | 17 | three of them are ASP.NET Core being stricter than its peers |
Measured against .NET 10.0.14, Go 1.21, PHP 8, Node 24 and Python 3. Numbers will move with versions; rerun rather than trusting the table.
Public, documented behaviour of public parsers. Nothing here is an undisclosed vulnerability, and nothing here targets a system I do not own. If you find something in this output that looks like a security bug in a product, report it to that product's security team before writing about it.
MIT. See LICENSE.