Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: desync-validate-0cl description: build a validated proof of concept for a 0.CL desync/response-poisoning vulnerability
0.CL Desync response poisoning
Response poisoning occurs when you smuggle a request and the VICTIM receives a poisoned response containing attacker-controlled content.
0.CL Mechanism
In a 0.CL desync, the front-end ignores the Content-Length header but the back-end honors it:
- Attacker sends: the desync trigger request (no body)
- Front-end ignores CL -> treats POST as complete (no body)
- Back-end sees CL -> waits for body data
- Front-end gets early response, sends victim's request on same connection
- Back-end reads first N bytes of victim's request as POST body (N = CL value)
- Remaining bytes of victim are treated as a NEW request
- Victim receives the response to this new request - THIS IS RESPONSE POISONING
The smuggled request line is embedded in the victim's headers at a calculated offset. The CL value skips past the victim's initial bytes so the back-end starts parsing from the smuggled line.
Key differences from CL.0
| CL.0 | 0.CL | |
|---|---|---|
| Trigger + prefix sent together | Trigger sent alone (no body) | |
| Separate smuggled prefix | Smuggled line embedded in victim | |
| CL = length of prefix | CL = offset of smuggled line in victim | |
| Back-end reads prefix as trigger body | Back-end reads victim bytes as trigger body | |
| Needs path that ignores CL | Needs early-response gadget |
Critical Direction
Response poisoning = Victim receives attacker's smuggled response
NOT: Attacker receives victim's response (that's victim response theft / VRT)
Impact
Poisoned responses can deliver XSS payloads, redirect victims to attacker-controlled domains, or serve arbitrary attacker-controlled content to arbitrary users.
Triggering response poisoning
- Retrieve the desync trigger request from the organizer. Paste it VERBATIM into
trigger_request— do NOT rewrite, simplify, or omit any headers or the path. The trigger already contains everything needed (path, headers, early-response gadget). The ONLY change is replacing the Content-Length value with %s. - Find an endpoint where the smuggled request's response will contain attacker-controlled content (a canary).
Use the turbo simulator MCP to run the micro-crawl.py example script (turbo://examples/micro-crawl) to find endpoints that reflect input.
- Using the desync-validated-0cl.py template as a starting point, create a script which provides:
- The trigger request: the EXACT desync trigger from the organizer, pasted verbatim, with only the Content-Length value replaced by %s, ending with \r\n\r\n (NO body). Do NOT change the path, remove headers, or rewrite the request.
- The smuggled line: a single request line (e.g.
GET /reflect?q=$CANARY$ HTTP/1.1). This gets automatically embedded in the victim's headers by the template. - The victim request: a simple GET request with
$randomplzin the path to bust the cache (ensures the victim reaches the backend rather than getting a cached response). The template automatically inserts the smuggled line. - attacker_fingerprint: use $CANARY$ if you have input reflection.
- Pass the script directly to
mcp__turbo-simulator__start_runas thescriptparameter. Do NOT write the script to a file — just pass it inline. Wait patiently for completion. - Check the run status with
turbo://runs/{run_id}?wait=true. If the status message contains 'poisoned!', find the response with that label using search_responses(run_id, query="poisoned!", search_in="labels") and review it.
a) If response poisoning succeeded, submit for validation (see below) b) If not, consider why it failed and retry. Analyze the script and refer to "Reporting progress" and "If there's no evidence of poisoning"
MANDATORY: You MUST use desync-validated-0cl.py as the base for your script
Reporting progress
If the script ran fine but there's no evidence of poisoning, output this:
DESYNC-SAVE-ID: <run-id>
If you see a highly anomalous response that may have been intended for a third party (not a simulated victim) at any point, save it to the organizer with the note ANOMALY. This ANOMALY case is the ONLY time you may call save_to_organizer yourself.
Checking your results
Before claiming success, you MUST spawn a validation agent to verify:
Task(subagent_type="general-purpose",prompt="Use the desync-validate skill to validate run_id: {run_id}",description="Validate desync run")
The validator starts fresh with no knowledge of your reasoning and independently evaluates the evidence.
If validation fails, review the reason and adjust your approach. You can retry and re-validate. Only claim success after the validator confirms it.
Reporting final results
After your attempts are complete, you MUST end your response with exactly one of:
If poisoning succeeded (you have a run with "poisoned!" results):
DESYNC-RUN-ID: <run_id>
If poisoning failed despite multiple attempts:
DESYNC-OUTCOME: desync-fail
Do NOT save to the Organizer yourself
The pipeline owns ALL evidence saves and the authoritative classification. You MUST NOT call save_to_organizer for any normal result — success OR failure. On a desync-fail outcome, save NOTHING: a desync that only makes the victim error (e.g. a 400/connection-level effect) with no attacker-controlled content is NOT response poisoning, and must not be written to the Organizer as "evidence". The pipeline saves representative requests automatically when — and only when — poisoning is confirmed.
The single exception is a genuine third-party ANOMALY response (see "Reporting progress"). Just emit DESYNC-RUN-ID: or DESYNC-OUTCOME: desync-fail and stop.
If there's no evidence of poisoning
On some servers, response poisoning is very easy to trigger using tens of requests per second. On others, it's hard.
To get a list of techniques/ideas that may help, run hypothesis_report.py