$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-ssrfHunting 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 De
| 1 | ## Crown Jewel Targets |
| 2 | |
| 3 | 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: |
| 4 | |
| 5 | - **Cloud-hosted SaaS products** (GCP metadata at `169.254.169.254` or `metadata.google.internal`, AWS IMDSv1) |
| 6 | - **Kubernetes/orchestration platforms** — aggregated API servers, metrics-server, kubelet endpoints expose privileged cluster operations |
| 7 | - **Internal developer tooling** — CI/CD, workflow orchestration (Flyte, Argo), admin panels not exposed externally |
| 8 | - **Link preview / URL fetching features** — Reddit-style preview APIs, Slack-style unfurling, media processors |
| 9 | - **Dataset/file import pipelines** — anything that fetches remote URLs on behalf of a user |
| 10 | - **Enterprise self-hosted software** (GitHub Enterprise, GitLab) — SSRF frequently chains to RCE via internal services |
| 11 | |
| 12 | Payouts are highest when SSRF reaches: cloud credentials → account takeover, internal admin APIs → data exfil, or chains to RCE. |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | ## OOB-Or-It-Didn't-Happen Gate (Read First) |
| 17 | |
| 18 | **Claims of blind SSRF require an out-of-band (OOB) confirmation. Always. No exceptions.** |
| 19 | |
| 20 | 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. |
| 21 | |
| 22 | ### What is NOT confirmation of SSRF |
| 23 | |
| 24 | - 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. |
| 25 | - 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. |
| 26 | - A delayed response when the URL is sent. Delay can come from DNS resolution attempts within the parser, not from completed HTTP fetches. |
| 27 | |
| 28 | ### What IS confirmation of SSRF |
| 29 | |
| 30 | - A DNS lookup for your unique Collaborator subdomain appears in the OOB listener. |
| 31 | - An HTTP request to your Collaborator HTTP endpoint with the server's source IP and User-Agent. |
| 32 | - For SSRF in JavaScript-execution contexts (PDF renderers, headless browsers), a fetch from the server to your callback URL. |
| 33 | |
| 34 | ### Default workflow |
| 35 | |
| 36 | 1. **Plant the Collaborator subdomain first** (sub-tag it per sink: `dlsrcurl.<collab>`, `import.<collab>`, etc., so callbacks tell you which sink fired). |
| 37 | 2. **Send the request** to the target endpoint. |
| 38 | 3. **Wait 30–120 seconds**, then poll the OOB listener. |
| 39 | 4. **Only after a confirmed callback** do you claim SSRF. |
| 40 | 5. If zero callbacks across all sub-tagged sinks: SSRF claims must be retracted, even if error messages echo URLs. |
| 41 | |
| 42 | **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. |
| 43 | |
| 44 | --- |
| 45 | |
| 46 | ## Attack Surface Signals |
| 47 | |
| 48 | ### URL Patterns to Hunt |
| 49 | ``` |
| 50 | /api/*/preview |
| 51 | /api/*/fetch |
| 52 | /api/*/import |
| 53 | /api/*/webhook |
| 54 | /api/*/proxy |
| 55 | /api/*/render |
| 56 | /api/*/link |
| 57 | /api/*/screenshot |
| 58 | /api/*/export |
| 59 | /api/*/validate |
| 60 | ?url= |
| 61 | ?uri= |
| 62 | ?endpoint= |
| 63 | ?redirect= |
| 64 | ?src= |
| 65 | ?source= |
| 66 | ?feed= |
| 67 | ?host= |
| 68 | ?target= |
| 69 | ?dest= |
| 70 | ?file= |
| 71 | ?path= |
| 72 | ?callback= |
| 73 | ?image= |
| 74 | ?load= |
| 75 | ?fetch= |
| 76 | ``` |
| 77 | |
| 78 | ### JS Patterns (in client-side code) |
| 79 | ```javascript |
| 80 | // Look for these in JS bundles |
| 81 | fetch(userInput) |
| 82 | axios.get(params.url) |
| 83 | XMLHttpRequest + variable URL |
| 84 | url: req.body.url |
| 85 | src: params.source |
| 86 | href: query.endpoint |
| 87 | ``` |
| 88 | |
| 89 | ### Response Header Signals |
| 90 | ``` |
| 91 | X-Forwarded-For headers echoed back |
| 92 | Server: internal-service |
| 93 | Via: 1.1 internal-proxy |
| 94 | X-Cache headers revealing internal hostname |