
10/6/2025 • 5 min read
SSRF Attacks: OWASP Top 10 Explained
Estimated reading time: ~9 minutes
The Problem
The 2019 Capital One breach exposed 100M+ records. The root cause? A simple SSRF vulnerability.
Server-Side Request Forgery (SSRF) attacks are subtle but devastating, allowing attackers to make the server perform unintended requests to internal or external resources. This is why SSRF is included in the OWASP Top 10.
What It Is
Attacker tricks a server into making unintended requests, often to internal services. The server acts as a proxy, making requests on behalf of the attacker.
How SSRF Works
- User Input: Attacker provides a URL in application input
- Server Request: Application makes HTTP request to that URL
- Unintended Access: Server accesses internal resources or external sites
- Information Disclosure: Attacker receives response or causes side effects
Types of SSRF
- Basic SSRF: Server makes request and returns response to attacker
- Blind SSRF: Server makes request but doesn't return response
- Semi-Blind SSRF: Server makes request and returns partial information
Examples
1. Accessing AWS Metadata Endpoints
Problem: Server can access internal AWS metadata service
Impact: Cloud credential theft
Example: http://169.254.169.254/latest/meta-data/iam/security-credentials/
Related: This can lead to Security Misconfiguration issues if the stolen credentials have excessive permissions.
2. Reaching Internal APIs
Problem: Server can access internal services not meant for public
Impact: Internal service compromise
Example: http://localhost:8080/admin/users or http://192.168.1.100:3306
3. Port Scanning Internal Networks
Problem: Server can probe internal network ports
Impact: Network reconnaissance and service discovery
Example: http://192.168.1.1:22, http://192.168.1.1:80, etc.
4. File System Access
Problem: Server can read local files via file:// protocol
Impact: Sensitive file disclosure
Example: file:///etc/passwd or file:///etc/shadow
5. External Service Abuse
Problem: Server can make requests to external services
Impact: Service abuse, DDoS, or information gathering
Example: http://internal-service:8080/admin/delete-all-data
Why It Matters
Cloud Credential Theft
- AWS IAM Roles: Access to temporary credentials
- Azure Managed Identity: Service principal access
- Google Cloud: Service account keys
- Kubernetes: Service account tokens
Network Pivoting
- Internal Service Access: Bypass network segmentation
- Lateral Movement: Access other internal systems
- Data Exfiltration: Send data to external servers
- Service Abuse: Use internal services maliciously
Business Impact
- Data Breaches: Access to internal databases and services
- Service Disruption: Abuse of internal services
- Compliance Violations: Unauthorized access to sensitive systems
- Financial Loss: Cloud resource abuse and data theft
How to Prevent It
1. Whitelist Outbound Requests
// Example: URL whitelist validation
const allowedHosts = [
'api.example.com',
'cdn.example.com',
'images.example.com'
];
function validateUrl(url) {
try {
const parsedUrl = new URL(url);
// Check if host is in whitelist
if (!allowedHosts.includes(parsedUrl.hostname)) {
throw new Error('Host not allowed');
}
// Block internal IP ranges
if (isInternalIP(parsedUrl.hostname)) {
throw new Error('Internal IP not allowed');
}
return true;
} catch (error) {
throw new Error('Invalid URL');
}
}
function isInternalIP(hostname) {
const internalRanges = [
/^127\./, // localhost
/^10\./, // 10.0.0.0/8
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
/^192\.168\./, // 192.168.0.0/16
/^169\.254\./, // link-local
/^::1$/, // IPv6 localhost
/^fc00:/, // IPv6 private
/^fe80:/ // IPv6 link-local
];
return internalRanges.some(range => range.test(hostname));
}
2. Validate and Sanitize URLs
# Example: Python URL validation
import re
from urllib.parse import urlparse
def validate_url(url):
try:
parsed = urlparse(url)
# Block dangerous protocols
if parsed.scheme not in ['http', 'https']:
raise ValueError('Only HTTP/HTTPS allowed')
# Block internal IPs
if is_internal_ip(parsed.hostname):
raise ValueError('Internal IPs not allowed')
# Block localhost variations
if parsed.hostname in ['localhost', '0.0.0.0']:
raise ValueError('Localhost not allowed')
return True
except Exception as e:
raise ValueError(f'Invalid URL: {str(e)}')
def is_internal_ip(hostname):
internal_patterns = [
r'^127\.',
r'^10\.',
r'^172\.(1[6-9]|2[0-9]|3[0-1])\.',
r'^192\.168\.',
r'^169\.254\.'
]
return any(re.match(pattern, hostname) for pattern in internal_patterns)
3. Segment Internal Services
# Example: Network segmentation
apiVersion: v1
kind: NetworkPolicy
metadata:
name: deny-internal-access
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: TCP
port: 443 # Only allow HTTPS to external
- to:
- namespaceSelector:
matchLabels:
name: allowed-internal
ports:
- protocol: TCP
port: 8080
4. Use Outbound Proxies
// Example: Proxy configuration
const proxy = require('http-proxy-middleware');
const proxyOptions = {
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
},
onProxyReq: (proxyReq, req, res) => {
// Add security headers
proxyReq.setHeader('X-Forwarded-For', req.ip);
proxyReq.setHeader('User-Agent', 'SecureProxy/1.0');
}
};
app.use('/api', proxy(proxyOptions));
5. Implement Request Timeouts and Limits
// Example: Request timeout and size limits
const axios = require('axios');
const secureHttpClient = axios.create({
timeout: 5000, // 5 second timeout
maxContentLength: 1024 * 1024, // 1MB max response size
maxRedirects: 3,
validateStatus: (status) => status < 400
});
async function makeSecureRequest(url) {
try {
const response = await secureHttpClient.get(url);
return response.data;
} catch (error) {
if (error.code === 'ECONNABORTED') {
throw new Error('Request timeout');
}
throw error;
}
}
6. Monitor and Log Outbound Requests
// Example: Request logging and monitoring
function logOutboundRequest(url, response, error) {
const logEntry = {
timestamp: new Date().toISOString(),
url,
status: response?.status || 'error',
error: error?.message,
userAgent: 'SecureApp/1.0'
};
// Log to security monitoring system
securityLogger.info(logEntry);
// Alert on suspicious patterns
if (isSuspiciousRequest(url)) {
alertSecurityTeam(logEntry);
}
}
function isSuspiciousRequest(url) {
const suspiciousPatterns = [
/169\.254\.169\.254/, // AWS metadata
/192\.168\./, // Private network
/10\./, // Private network
/172\.(1[6-9]|2[0-9]|3[0-1])\./, // Private network
/localhost/,
/127\.0\.0\.1/
];
return suspiciousPatterns.some(pattern => pattern.test(url));
}
Tools
- Rafter: Scans for SSRF vulnerabilities
- OWASP ZAP: Automated SSRF testing
- Burp Suite: Manual SSRF testing
- Nmap: Network scanning for SSRF testing
- SSRFmap: Automated SSRF exploitation tool
Conclusion
SSRF is subtle but devastating. Validate every outbound request, implement proper network segmentation, and monitor for suspicious patterns.
Next Steps:
- Audit your outbound request handling
- Implement URL validation and whitelisting
- Set up network segmentation
- Run a Rafter scan to identify potential SSRF vulnerabilities in your application