cybersecurity hacking security devops networking software development Slow JSON Stream: 64 connections at 1 B/s takes down PHP/Laravel in under 2 minutes Daniel GarcĂa (cr0hn) 27 Jun 2026 I’ve been researching a class of attack that I find particularly interesting because it combines something that already exists (slow HTTP attacks) with something nobody had studied properly: the body reader of modern HTTP frameworks. The result is called Slow JSON Stream , and the TL;DR is this: with 64 simultaneous connections sending 1 byte per second from a single machine, you can leave a PHP/Laravel server unresponsive in under 2 minutes. No special tools. No prior knowledge required. Less than 1 kbps of total bandwidth. And 90% of the frameworks I tested are vulnerable by default. How it works The idea is simple. When an HTTP server receives a request with Content-Type: application/json , the framework has to read the complete body before it can process it. The JSON parser needs the closing } or ] to know the document is complete. Slow JSON Stream abuses this: we open a connection, send a valid JSON prefix, something like {"items":[{"a":1}, , and drip it at 1 byte per second using Transfer-Encoding: chunked . We never send the closing token. The server waits. And waits. And waits. Each of those connections occupies a worker thread, a goroutine, or an event loop slot. Open 64 of them simultaneously and the server’s worker pool is exhausted. Legitimate requests pile up in the queue, time out, and start failing. POST /api/data HTTP/1.1 Host: target.com Content-Type: application/json Transfer-Encoding: chunked 1\r\n {\r\n 1\r\n "\r\n ... (1 byte per second, forever) The payload is syntactically valid JSON at all times. The parser can’t reject it because every byte received so far is a legal prefix of a complete JSON document. It’s blocked waiting for the closing token that never arrives. How is this different from Slowloris? Slowloris (2009) is the grandfather of slow HTTP attacks. It targets the header phase : it never sends the final \r\n that terminates the headers. But that attack has been mitigated for 15 years. nginx, Caddy, Apache… all have client_header_timeout set to 10-60 seconds by default. Slow JSON Stream targets the body reader , a layer that virtually no framework protects. The body equivalent of client_header_timeout ( client_body_timeout , MinRequestBodyDataRate , ReadTimeout ) either doesn’t exist or is set to minutes in the default config of 30 out of the 32 frameworks I tested. The closest historical predecessor is R.U.D.Y. / Slow POST (2011) , but there are key differences: R.U.D.Y. uses a declared Content-Length . Slow JSON Stream uses Transfer-Encoding: chunked , so there’s no declared size, and the server waits for the chunk terminator 0\r\n\r\n that never arrives. R.U.D.Y. sends arbitrary bytes. Slow JSON Stream sends a valid JSON prefix, blocking both the HTTP layer and the JSON parser simultaneously. R.U.D.Y. is detectable by WAFs inspecting the Content-Length . Slow JSON Stream has no declared size and is indistinguishable from a slow mobile client uploading data. The results (spoiler: not great) I tested 41 targets : 32 application frameworks and 9 infrastructure components (proxies, WAFs, API gateways). Results: 37 out of 41 (90%) are vulnerable by default Only 4 resist: go-fiber, python-fastapi with streaming, Traefik, and Apache ModSecurity The worst cases PHP/Laravel is the most dramatic. With 64 connections, RSS (resident memory) grows at 258 MB/minute . A 512 MB container falls in under 2 minutes. Immediate impact from a single machine. Python/Flask and Ruby/Rails are worse in a different way: they don’t degrade gradually, they crash . Flask hits a 94% error rate at C=64. Rails hits 100%. The service simply stops responding to everything. .NET/ASP.NET Core (Kestrel) accumulates 66-120 MB/minute of RSS. The reason: MinRequestBodyDataRate is disabled by default (value null ). The “latently vulnerable” group Async frameworks (Node.js, Go, Rust, Python async…) are a different story. At C=64 you barely notice anything: goroutines and coroutines are cheap, consuming ~2 KB per blocked connection vs ~1 MB for a thread. But they have no mechanism to ever close those connections. The goroutines accumulate indefinitely. In production, where pools are typically exhausted at C=100-500, the same 64-connection attack can take the service down. Framework Tier Evidence PHP/Laravel 1 258 MB/min RSS, down in <2 min .NET/Kestrel 1 66-120 MB/min RSS Python/Flask 3 (crash) 94% error rate at C=64 Ruby/Rails 3 (crash) 100% error rate at C=64 Node.js (all) 2 No body timeout, slots held indefinitely Go (gin, echo…) 2 No body timeout, goroutines accumulate Rust (axum, actix) 2 No body timeout, async tasks accumulate go-fiber RESISTANT 4 KB buffer, kills connections in ~1s What works as a defense and what doesn’t A lot of things that look like defenses don’t actually work. The nginx default client_body_timeout 60s ...
The article describes a novel "Slow JSON Stream" attack, a slow HTTP body attack that exploits JSON parser behavior by sending a syntactically valid but never-completed JSON payload at an extremely low data rate (e.g., 1 B/s). This occupies server worker threads or event loop slots, exhausting the connection pool and causing denial of service with minimal bandwidth. The author states 90% of tested frameworks, including PHP/Laravel, are vulnerable by default due to missing or overly permissive body read timeouts.