October 23, 2026
Hunting Thread Exhaustion: Ischemia for HTTP/1.1 Partial-Entity Holds
Howdy cowboys! This write-up is a quick note about how and why I developed this tool. Although I’ve been using a version of this internally…

By denialdisk
3 min read
Howdy cowboys! This write-up is a quick note about how and why I developed this tool. Although I've been using a version of this internally for many years, this public release got some AI polish. Sorry hAIters.
While testing large-scope, on-premises deployments over the years, I kept noticing the exact same pattern: manually holding a POST request caused certain endpoints to hang indefinitely. Yes, even in 2026. This takes me back to when I was a teen lol. So many years later and it's still current.
The backend was using a synchronous, "thread per request" architecture (common in older Java, PHP-FPM, Apache Tomcat, or default Python/Flask setups). By sending a valid Content-Length header and deliberately withholding the final byte of the body, I could tie up a worker thread indefinitely. It's pure OG material
Doing this manually proves the concept, but try explaining "I slowed down the server" to a triage team and watch them close it as "Theoretical / Low Severity". To turn this into a validated, reproducible vulnerability, I needed to scale it without triggering WAF rate limits or exhausting my own local resources, so, I built Ischemia.
The Hunter's Problem with Existing Tools
When testing for resource exhaustion, you face three immediate hurdles:
- WAF Triggering: Blasting thousands of connections instantly trips rate limits or IPS signatures. You get a false negative because your IP is blocked, not because the server is resilient.
- Local Exhaustion: If your own machine runs out of ephemeral ports or file descriptors, you can't tell if the target is down or if your tool just crashed.
- Triage Rejection: You need to prove attribution. You must demonstrate that the latency was caused by your hold, not network jitter or a bad routing day.
How Ischemia Solves This
For Ischemia, Python's asyncio does the job perfectly. Ischemia doesn't just flood; it orchestrates. It is designed for evidentiary integrity.
- Paced Arming: Using
--arm-rateand--arm-concurrency, it trickles connections to the target, mimicking legitimate traffic spikes and bypassing naive WAF rate limits. - The Hold: It sends the headers and body, minus the final byte. The backend allocates a thread and blocks on
read(). - Release: It drops the final byte simultaneously across all armed connections.
- Attributable Metrics: This is the critical part for your report. Ischemia tracks
armed_ns,released_ns, andresponded_nsat the nanosecond level. It calculatesheaders_ms(the time from release to the first byte of the server's response).
Proving Impact to Triage
A report needs a clear before and after. Ischemia generates a JSON summary that gives triage teams undeniable proof.
Step 1: Run a baseline (no hold)
python3 ischemia.py --host target.com --port 443 --scheme https --endpoint /api/login --connections 500 --hold-seconds 0 --out baselinepython3 ischemia.py --host target.com --port 443 --scheme https --endpoint /api/login --connections 500 --hold-seconds 0 --out baselineStep 2: Run the hold
python3 ischemia.py --host target.com --port 443 --scheme https --endpoint /api/login --connections 2000 --arm-concurrency 100 --arm-rate 50 --hold-seconds 30 --data 'user=test&pass=test' --out attackpython3 ischemia.py --host target.com --port 443 --scheme https --endpoint /api/login --connections 2000 --arm-concurrency 100 --arm-rate 50 --hold-seconds 30 --data 'user=test&pass=test' --out attackHere is what a successful, vulnerable hit actually looks like in the output:
How to Hunt This Tomorrow
- Identify Candidates: Look for endpoints that process heavy payloads (login, file upload, password reset, report generation). These are most likely to be routed to synchronous backend workers.
- Scale with Ischemia:
- Raise your local file descriptors first (
ulimit -n 65535). Seriously, do this, or you'll just exhaust your own file descriptors and wonder what happened 😂. - Start with a low
--arm-rate(e.g., 20-50 req/sec) to find the WAF threshold. - Compare the
headers_msdelta between a--hold-seconds 0run and a--hold-seconds 30run.
3. Chain the Impact: Don't just report DoS. If the login endpoint is exhausted, can you prevent a user from resetting their password while you brute-force it? If the API is down, does it fail open or break a dependent business workflow?
Mitigation (For the defenders reading)
If you are securing an application, fix this at the edge:
- Enforce strict
client_body_timeout(e.g., 10s) in Nginx or HAProxy. - Ensure your reverse proxy fully buffers the request body in memory/disk before passing it to the backend application server.
- Migrate synchronous workers to asynchronous, event-driven I/O models where idle connections do not consume dedicated threads.
All right, that's all for today folks lol. Hack the planet!