flawopen.com/Vulnerabilities/Server-Side Request Forgery
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.
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.
# 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
# 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
http://169.254.169.254/latest/meta-data/.HttpTokens=required and HttpPutResponseHopLimit=1 to block unauthorized metadata queries.302 Redirect to 127.0.0.1.