Server-Side Request Forgery (SSRF) is a vulnerability that allows an attacker to induce the server-side application to make requests to an unintended location. This can be used to access internal services behind firewalls, scan internal networks, or retrieve files from the server itself.
This tool generates previews of websites by fetching their content server-side and displaying a summary.
This service is vulnerable to SSRF because it doesn't validate or restrict the URLs that can be requested. Try these payloads:
http://localhost:8080/latest/meta-data/iam/security-credentials/instance-role
- Access cloud instance metadatahttp://localhost:8081/api/settings
- Access internal admin APIfile:///etc/passwd
- Read local files on the serverhttp://10.0.0.1
- Scan internal networkhttp://169.254.169.254/latest/meta-data/
- Common cloud metadata endpointThis is a secure version of the URL preview tool that implements proper validation and restrictions.
Try the same exploit URLs from the vulnerable service - they will all be blocked!
This diagram shows a typical cloud/network architecture and how SSRF can be used to bypass security boundaries.
This attack works because the internal network trusts requests from the web server, but the web server is processing untrusted user input.
Cloud providers expose instance metadata at well-known IP addresses:
http://169.254.169.254/latest/meta-data/
- AWS EC2http://metadata.google.internal/computeMetadata/v1/
- Google Cloudhttp://169.254.169.254/metadata/v1/
- DigitalOceanhttp://169.254.169.254/metadata/instance/
- AzureAttackers can use SSRF to access these endpoints and retrieve sensitive information like IAM credentials.
Organizations often have internal services that aren't meant to be publicly accessible:
http://admin.internal/
)http://monitoring.internal:8080/
)http://dev.internal/
)If the application supports the file:// protocol, attackers can read local files:
file:///etc/passwd
- User informationfile:///proc/self/environ
- Process environment variablesfile:///var/www/html/config.php
- Application configuration filesfile:///home/user/.ssh/id_rsa
- SSH private keysSSRF can be used to scan internal ports and map the network:
http://localhost:22
- SSHhttp://localhost:3306
- MySQLhttp://10.0.0.1:8080
- Internal web serviceResponse timing and error messages can reveal if ports are open or closed.
Various techniques to bypass URL filtering:
http://127.0.0.1
→ http://%31%32%37%2e%30%2e%30%2e%31
http://0177.0.0.1
(octal), http://0x7f.0.0.1
(hex)https://attacker.com/redirect
which redirects to http://internal.service/
gopher://
, dict://
, ftp://
Implement thorough URL validation:
// Whitelist allowed schemes $allowed_schemes = ['http', 'https']; $parsed_url = parse_url($url); if (!isset($parsed_url['scheme']) || !in_array($parsed_url['scheme'], $allowed_schemes)) { return 'Invalid URL scheme'; } // Whitelist allowed domains $allowed_domains = ['example.com', 'trusted-domain.com']; $hostname = $parsed_url['host']; $is_allowed = false; foreach ($allowed_domains as $domain) { if (strcasecmp($hostname, $domain) === 0 || preg_match('/\\.' . preg_quote($domain, '/') . '$/', $hostname)) { $is_allowed = true; break; } } if (!$is_allowed) { return 'Domain not in whitelist'; }
Prevent requests to internal networks:
// Resolve hostname to IP $ip = gethostbyname($hostname); // Check against private IP ranges $private_ranges = [ '10.0.0.0/8', // Private network '172.16.0.0/12', // Private network '192.168.0.0/16', // Private network '127.0.0.0/8', // Localhost '169.254.0.0/16', // Link-local // ... other restricted ranges ]; foreach ($private_ranges as $range) { if (ip_in_range($ip, $range)) { return 'IP address not allowed'; } }
Whitelist allowed resources rather than trying to block malicious ones:
// Instead of trying to block all internal IPs, // only allow specific external domains or APIs $allowed_endpoints = [ 'api.github.com', 'api.stripe.com', 'maps.googleapis.com' ]; if (!in_array($hostname, $allowed_endpoints)) { return 'Endpoint not allowed'; }
Restrict the application to only use necessary protocols:
// In PHP with curl curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); // In Java with URLConnection if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) { throw new IllegalArgumentException("Only HTTP(S) is allowed"); }
Isolate URL-fetching functionality in a separate service with limited privileges:
Prevent DNS rebinding attacks by validating the IP both before and after the request:
// Resolve the hostname once and use that IP for the actual request $hostname = parse_url($url, PHP_URL_HOST); $ip = gethostbyname($hostname); // In curl, use CURLOPT_RESOLVE to force the IP curl_setopt($ch, CURLOPT_RESOLVE, ["$hostname:443:$ip"]);