Weakness reference
CWE-918

Server-Side Request Forgery

Server-Side Request Forgery SSRF occurs when a web application fetches a URL or resource based on user input without properly validating the destination. An…

01Summary

Server-Side Request Forgery (SSRF) occurs when a web application fetches a URL or resource based on user input without properly validating the destination. An attacker can manipulate this input to make the server request internal resources, private APIs, or external systems it shouldn't access, potentially exposing sensitive data or enabling further attacks.

02How It Happens

The vulnerability arises when an application accepts a URL from a user (via query parameters, form fields, file uploads, or API requests) and uses it directly in a server-side HTTP request, file operation, or network call without validating the destination. The application may intend to fetch a remote image, document, or API response, but lacks controls to prevent requests to internal IP ranges (like 127.0.0.1 or 10.0.0.0/8), private cloud metadata endpoints, or other sensitive targets. An attacker can supply a crafted URL pointing to these restricted resources, and because the request originates from the server itself—not the attacker's browser—it bypasses network firewalls and access controls that would normally block external requests.

03Real-World Impact

SSRF can lead to unauthorized access to internal services, such as database admin panels, configuration servers, or cloud metadata endpoints that expose API keys and credentials. Attackers may also use the compromised server as a proxy to attack other systems on the internal network, scan for open ports, or trigger actions on internal APIs. In cloud environments, SSRF against metadata services (e.g., AWS IMDSv1) can leak temporary credentials with broad permissions, leading to account compromise and data exfiltration.

04Vulnerable & Fixed Patterns

Vulnerable pattern
import requests
from flask import Flask, request

app = Flask(__name__)

@app.route('/fetch-content')
def fetch_content():
    url = request.args.get('url')
    # User-supplied URL is fetched directly without validation
    response = requests.get(url, timeout=5)
    return response.text

Why it's vulnerable:
The url parameter is taken directly from user input and passed to requests.get() with no validation. An attacker can supply http://127.0.0.1:8080/admin or http://169.254.169.254/latest/meta-data/ to access internal resources.

Fixed pattern
import requests
from flask import Flask, request
from urllib.parse import urlparse
import ipaddress

app = Flask(__name__)

ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']

@app.route('/fetch-content')
def fetch_content():
    url = request.args.get('url')
    
    # Parse and validate the URL
    try:
        parsed = urlparse(url)
        hostname = parsed.hostname
        
        # Reject private/reserved IP ranges
        ip = ipaddress.ip_address(hostname)
        if ip.is_private or ip.is_loopback or ip.is_reserved:
            return "Invalid destination", 400
        
        # Allowlist known-safe domains
        if hostname not in ALLOWED_DOMAINS:
            return "Domain not allowed", 400
        
        response = requests.get(url, timeout=5)
        return response.text
    except (ValueError, ipaddress.AddressValueError):
        return "Invalid URL", 400
Vulnerable pattern
<?php
$url = $_GET['url'];
// User-supplied URL is fetched directly
$content = file_get_contents($url);
echo $content;
?>

Why it's vulnerable:
The $url parameter is passed directly to file_get_contents() without checking the destination. An attacker can supply http://127.0.0.1/admin or file:///etc/passwd to access local files or internal services.

Fixed pattern
<?php
$url = $_GET['url'];
$allowed_domains = ['api.example.com', 'cdn.example.com'];

// Parse and validate the URL
$parsed = parse_url($url);
$hostname = $parsed['host'] ?? '';

// Reject private/reserved IP ranges
$ip = gethostbyname($hostname);
if (filter_var($ip, FILTER_VALIDATE_IP, 
    FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
    die('Invalid destination');
}

// Allowlist known-safe domains
if (!in_array($hostname, $allowed_domains, true)) {
    die('Domain not allowed');
}

// Enforce HTTPS
if (($parsed['scheme'] ?? '') !== 'https') {
    die('Only HTTPS allowed');
}

$content = file_get_contents($url);
echo $content;
?>

05Prevention Checklist

Maintain an allowlist
of permitted domains or IP ranges; reject all others by default rather than trying to blacklist dangerous ones.
Validate and parse URLs
before use; check the scheme (reject file://, gopher://, etc.), hostname, and port.
Block private IP ranges
(127.0.0.1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and reserved addresses (169.254.x.x, 0.0.0.0).
Disable unnecessary protocols
in HTTP client libraries; disable redirects or limit them to allowlisted domains.
Use network segmentation
to restrict outbound connections from application servers to only necessary external services.
Monitor outbound requests
for unexpected destinations; log and alert on requests to internal IPs or unusual ports.

06Signs You May Already Be Affected

Check application logs for requests to internal IP addresses (127.0.0.1, 10.x.x.x, 172.16.x.x, 192.168.x.x) or cloud metadata endpoints (169.254.169.254). Review URL parameters in recent requests for suspicious patterns like localhost, internal, or IP addresses. If your application fetches remote content, audit the code paths that construct URLs to ensure user input is validated before use.

07Related Recent Vulnerabilities