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…
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
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.
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.