flawopen.com/Vulnerabilities/Server-Side Request Forgery

Server-Side Request Forgery (SSRF)

High Severity CWE-918 Modern Web & Cloud
ELI5 — The Proxy Waiter Trick

Imagine a restaurant customer tells a waiter, 'Please run next door to the market and bring me their special sauce.' The waiter happily walks over and gets it. But then the customer whispers, 'Please walk into the bank vault behind the kitchen counter and bring me the ledger.' The waiter has an employee badge to enter the vault, so he walks straight past the bank guards and hands the customer confidential financial secrets. In SSRF, the vulnerable backend server is the naive waiter fetching sensitive internal services on behalf of an untrusted client.

Target: Cloud metadata APIs & internal VPC services
Vector: User-supplied URLs in webhooks, PDF generators, image downloaders
Impact: IAM credential theft, cloud account takeover, remote command execution
Remediation: Strict IP parsing, private CIDR filtering, disabling HTTP redirects, IMDSv2

The Mechanism & Root Cause

SSRF occurs when a web application accepts a remote URL from a user and fetches it using backend HTTP libraries (e.g. Requests, Axios, HttpClient) without verifying that the resolved destination is a public address. Attackers supply internal IP addresses (127.0.0.1, 10.0.0.0/8, 169.254.169.254) or DNS hostnames that resolve to internal resources.

python_service.py (Vulnerable)Vulnerable
# VULNERABLE: Naively fetching user-controlled URL
import requests

def fetch_avatar(user_url):
    # Attacker passes: http://169.254.169.254/latest/meta-data/iam/security-credentials/
    # Server blindly fetches AWS metadata and returns temporary cloud keys
    response = requests.get(user_url, timeout=5)
    return response.content
python_service.py (Hardened)Hardened
# HARDENED: Resolve DNS, parse IP, and deny private/loopback/link-local CIDRs
import socket
import ipaddress
import urllib.parse
import requests

BLOCKED_NETWORKS = [
    ipaddress.ip_network('127.0.0.0/8'),
    ipaddress.ip_network('10.0.0.0/8'),
    ipaddress.ip_network('172.16.0.0/12'),
    ipaddress.ip_network('192.168.0.0/16'),
    ipaddress.ip_network('169.254.0.0/16'), # AWS/Azure/GCP Link-Local IMDS
]

def fetch_avatar_safe(user_url):
    parsed = urllib.parse.urlparse(user_url)
    if parsed.scheme not in ('http', 'https'):
        raise ValueError("Invalid URL scheme")
    
    # Resolve host to IP
    ip_str = socket.gethostbyname(parsed.hostname)
    ip_addr = ipaddress.ip_address(ip_str)
    
    for net in BLOCKED_NETWORKS:
        if ip_addr in net:
            raise PermissionError("Access to private/internal network addresses is prohibited")
            
    # Fetch pinning the IP or disabling redirects
    return requests.get(user_url, allow_redirects=False, timeout=3).content

The Attack & Exploit Sequence

Defensive Engineering & Prevention Rules

Explore related security topics and post-mortems: Complete Security Directory →