Server-Side Request Forgery, or SSRF, is one of the most underestimated vulnerabilities in modern web applications. The core problem is deceptively simple. An application fetches a URL on the user's behalf, for example to load a preview image or fire a webhook. If the server never checks where that request actually goes, an attacker can redirect it. Instead of reaching the intended target, the request hits internal services, local files, or the cloud metadata endpoint that hands out temporary access keys.
What makes SSRF so dangerous is the borrowed trust. The request originates from the server itself, so it comes from inside the network. Firewalls, private subnets, and internal services that should never be reachable from the internet are often wide open to the server. An attacker who exploits SSRF borrows exactly that inside view.
This article explains SSRF from the ground up: the attack types (in-band, blind, semi-blind), concrete payloads for each target category, the common filter bypasses verbatim, and above all TOCTOU-safe defense code for Node.js, Python and Java that you can adopt directly. We also place SSRF precisely within the OWASP Top 10 across editions, separate it cleanly from the frequently confused CSRF, and look at real cases such as Capital One with the factual care they deserve.
What is Server-Side Request Forgery?
The OWASP Foundation defines SSRF as an attack in which the adversary abuses server-side functionality to trigger requests to unintended destinations. Formally, MITRE classifies it as CWE-918 (Server-Side Request Forgery). The core definition there: a web server receives a URL or similar reference from an upstream component (typically user input) and fetches the contents of that URL, without sufficiently ensuring that the request is sent to the expected destination.
Conceptually, SSRF is a flavor of the confused deputy problem. That is why CWE-918 is a child of CWE-441 (Unintended Proxy or Intermediary) and CWE-610 (Externally Controlled Reference to a Resource in Another Sphere). In plain terms, the server becomes a confused deputy that exercises its own privileges on behalf of a stranger without realizing it.
SSRF appears anywhere an application fetches a user-influenced URL. Typical triggers include:
- Image or file downloads from a supplied URL (preview thumbnails, avatars, import features).
- Custom webhooks where the user provides a callback URL.
- Inter-service communication in microservice architectures.
- URL preview features (link unfurling in chats and comments).
- Document or PDF generators that pull in embedded resources.
A minimal vulnerable example
The MITRE CWE-918 page shows the base pattern in just three lines of PHP. The code takes a URL and fetches it without any validation:
$url = $_GET['url'];
$response = file_get_contents($url);
echo $response;
As long as the user supplies https://example.com/image.png, everything works as intended. But if they supply http://169.254.169.254/latest/meta-data/, the server fetches the cloud metadata service and returns its response. That missing destination check is the entire vulnerability.
The three types of SSRF
For practical analysis, the key question is whether and how the attacker gets to see the response of the forced request. That yields three types.
In-band SSRF (basic SSRF)
In classic in-band SSRF, the response to the internal request is returned directly to the attacker. Supply a URL pointing at an internal admin panel, and its HTML shows up in the server response. This is the most convenient variant for the attacker and the most visible for the defender.
Blind SSRF
With blind SSRF, the attacker can trigger the request but does not get the response back. Detection and exploitation then rely on side effects: out-of-band interactions such as a DNS lookup or an HTTP request to an attacker-controlled domain. If that domain records an incoming lookup, it proves the server actually issued the request. Testers use collaborator-style services that log exactly these incoming interactions.
Semi-blind SSRF
The middle ground: the attacker does not see the full response body, but does observe indirect signals such as status codes, response lengths, or response times. These side channels leak information, for example whether an internal port is open (fast response) or closed (timeout). This pattern underpins internal port scanning via SSRF, sometimes labeled XSPA (Cross-Site Port Attack).
Attack targets with concrete payloads
SSRF is never an end in itself. The danger comes from what the attacker achieves with the borrowed inside view. The following payloads show the classic target categories using a vulnerable parameter as an example.
Attacking the server itself
Using the loopback address, the attacker forces the server to talk to itself. This exposes internal endpoints that are not routed externally at all:
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
stockApi=http://localhost/admin
An admin area that is only reachable internally, and therefore often runs without extra authentication, becomes accessible through the public endpoint this way.
Attacking back-end systems
Likewise, internal, non-publicly routable systems in a private subnet can be reached, systems that are run on the blind assumption that they are only accessible internally:
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
stockApi=http://192.168.0.68/admin
Cloud metadata: the highest-value target
In the cloud, the instance metadata service sits at the link-local address 169.254.169.254. It serves configuration data and, critically, temporary access keys for the IAM role the instance runs under. On AWS in its first version (IMDSv1), a simple GET request is enough to grab the credentials:
GET /user/image?imgUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/Admin-Role
Whoever captures those temporary keys can impersonate the instance against the cloud API and, depending on the role's permissions, reach storage buckets, databases, and other services. This mechanism is what makes SSRF so severe in cloud environments. How AWS counters it with IMDSv2 is covered in the defense section below. GCP and Azure have their own metadata endpoints that deliberately cannot be fetched with a plain GET (more on that below).
Local files and internal services
The file:// scheme lets an attacker read local files, a class of input-driven flaw it shares with Local File Inclusion and RFI, provided the application's HTTP client supports the scheme:
url=file:///etc/passwd
Unauthenticated internal services are popular targets too. These include databases with an HTTP interface (such as MongoDB at http://localhost:28017/) and in-memory stores like Redis, which can even be escalated to remote code execution via the gopher:// scheme if the attacker smuggles in raw protocol commands. For pure reconnaissance, a handful of default targets already suffices:
http://localhost:80
http://127.0.0.1:80
http://0.0.0.0:80
Filter bypasses: why naive defenses fail
Many applications try to prevent SSRF with a blocklist, banning strings like 127.0.0.1 or localhost. OWASP warns against exactly this: do not mitigate SSRF with a deny list or regular expressions. The reason is the many equivalent ways to write the same address.
Alternative IP notations
The loopback address 127.0.0.1 can be written in many ways that map to the same number and are accepted by URL parsers:
http://2130706433/ # decimal
http://0177.0.0.1/ # octal
http://0x7f000001 # hexadecimal
http://127.127.127.127 # any address in 127.0.0.0/8 is loopback
http://127.1 # shortened notation
The decimal variant checks out mathematically: 127 times 256 cubed plus 1 equals 2130706433. That the octal and hexadecimal notations in particular have been exploited in the wild is documented by CVE-2016-4029: WordPress before 4.5 did not consider octal and hexadecimal IP address formats when deciding whether an address was internal, letting attackers slip past its SSRF protection with a crafted address (nvd.nist.gov/vuln/detail/CVE-2016-4029).
IPv6 tricks and rebinding domains
Blocklists can also be dodged via IPv6 and via wildcard DNS services:
http://[::]:80/ # unspecified address
http://[::ffff:127.0.0.1] # IPv4-mapped IPv6
localtest.me # resolves to 127.0.0.1
company.127.0.0.1.nip.io # nip.io returns the embedded IP
DNS rebinding and the TOCTOU problem
The most sophisticated bypass is DNS rebinding. The idea: the attacker registers a domain that returns a harmless public IP on first resolution (so validation passes), but 127.0.0.1 or 169.254.169.254 on a second resolution moments later. If the application validates the URL, then re-resolves the name and connects to the second result, it walks right past the check into the internal network.
This is a classic time-of-check-to-time-of-use (TOCTOU) problem: between the moment of checking and the moment of use, reality changes. This is exactly where many well-meaning code samples fail, ones that only check the hostname against an allowlist and then hand the connection to an HTTP client that resolves the name fresh (and unchecked). The fix, shown in the defense section, is: resolve once, validate the IP, then connect to that exact validated IP.
Redirect bypass: the bridge to open redirect
Another bypass uses redirects. The attacker supplies an allowed URL that passes validation. But that URL responds with a 3xx redirect to an internal target. If the application's HTTP client follows the redirect automatically, the request still lands on the blocked destination:
url=https://harmless-site.example/redirect
This becomes especially dangerous when the target application itself contains an open redirect vulnerability: the SSRF filter then validates a seemingly trusted first-party domain that nevertheless forwards the attacker inward via redirect. An open redirect is therefore an amplifier for SSRF, which is why the two vulnerability classes belong together.
Whitelist bypasses via URL parsing
Even domain-level allowlists are vulnerable when the application parses the URL naively. Classic parser tricks:
https://expected-host:password@evil-host # userinfo fakes the host
https://evil-host#expected-host # fragment fakes substring checks
https://expected-host.evil-host # subdomain trick, real host is evil-host
These discrepancies arise because different URL libraries interpret the same string differently. A substring check for expected-host says nothing about which host the client ultimately connects to.
Preventing SSRF effectively
The bypasses lead to the central insight: pure blocklists and regex checks are not enough. Effective protection builds on several principles that OWASP and MDN state consistently.
The core principles
- Allowlist over deny list. When the allowed targets are known and finite, validate strictly against a positive list of scheme, port, and destination. Deny lists are prone to bypass.
- Do not accept complete URLs from the user. OWASP puts it plainly: URLs are hard to validate reliably. The most radical and safest form is to give the user no free URL input at all, only a choice among predefined targets.
- Restrict URL schemes. Allow
httpandhttpsonly. Consistently blockfile://,gopher://,dict://and other schemes. - Do not follow redirects automatically. Disable automatic redirect following, or re-validate every redirect target.
- Least privilege and network segmentation. The outbound service should run with minimal permissions and, by a default-deny firewall policy, only be able to reach the destinations it genuinely needs.
As a last line of defense, not the primary control, the OWASP cheat sheet also recommends blocking at least the following ranges:
| Target | Block |
|---|---|
| AWS/GCP/Azure metadata | 169.254.169.254, metadata.google.internal |
| Localhost | 127.0.0.0/8, 0.0.0.0/8, ::1/128 |
| RFC1918 private | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 |
| Multicast | 224.0.0.0/4, ff00::/8 |
The decisive point: validate in a TOCTOU-safe way
Many code samples online only check the hostname of a parsed URL against an allowlist and then leave the actual connection to the HTTP client. That is unsafe because of DNS rebinding: the client re-resolves the name and may get a different result between the check and the connection. The correct approach is a three-step process: check the scheme, resolve the hostname once and validate every returned IP against the blocked ranges, then connect to that exact validated IP without resolving again. The original hostname is retained only for the Host header and the TLS check.
Node.js: TOCTOU-safe
The following example resolves the hostname exactly once, validates the IP, and then connects to that precise address. The Host header and the TLS servername keep the original name so virtual hosts and certificate validation still work:
const dns = require('dns').promises;
const net = require('net');
const http = require('http');
const https = require('https');
const { URL } = require('url');
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);
function isBlockedIp(ip) {
if (net.isIPv4(ip)) {
const p = ip.split('.').map(Number);
if (p[0] === 10) return true; // 10.0.0.0/8
if (p[0] === 127) return true; // 127.0.0.0/8 loopback
if (p[0] === 0) return true; // 0.0.0.0/8
if (p[0] === 169 && p[1] === 254) return true; // 169.254.0.0/16 (IMDS!)
if (p[0] === 172 && p[1] >= 16 && p[1] <= 31) return true; // 172.16.0.0/12
if (p[0] === 192 && p[1] === 168) return true; // 192.168.0.0/16
if (p[0] >= 224) return true; // multicast/reserved
return false;
}
const v6 = ip.toLowerCase();
if (v6 === '::1' || v6 === '::') return true;
if (v6.startsWith('fe80')) return true; // link-local
if (v6.startsWith('fc') || v6.startsWith('fd')) return true; // unique local fc00::/7
const mapped = v6.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
if (mapped) return isBlockedIp(mapped[1]);
return false;
}
async function safeFetch(userUrl) {
const url = new URL(userUrl);
// 1. Restrict the scheme strictly
if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
throw new Error(`Protocol not allowed: ${url.protocol}`);
}
// 2. Resolve the hostname ONCE and check the IP
const { address } = await dns.lookup(url.hostname);
if (isBlockedIp(address)) {
throw new Error(`Target IP not allowed: ${address}`);
}
// 3. Connect to EXACTLY the validated IP (rebinding-safe)
const client = url.protocol === 'https:' ? https : http;
const options = {
host: address, // bind to the validated IP
servername: url.hostname, // correct TLS SNI
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
headers: { Host: url.hostname },
};
return new Promise((resolve, reject) => {
const req = client.get(options, (res) => {
// 4. Do not follow redirects automatically
if (res.statusCode >= 300 && res.statusCode < 400) {
res.destroy();
return reject(new Error(`Redirect to ${res.headers.location} blocked`));
}
let body = '';
res.on('data', (c) => (body += c));
res.on('end', () => resolve(body));
});
req.on('error', reject);
});
}
A note for context: Node's native http/https module does not follow redirects automatically, which is a safe default here. If you use higher-level clients such as axios or got, you must explicitly disable redirect following.
Python (requests): TOCTOU-safe
In Python we resolve the hostname with socket.getaddrinfo, validate every returned IP with the ipaddress module, and pin the connection to the validated IP via a custom adapter. The server_hostname ensures TLS certificate validation still runs against the original name:
import ipaddress
import socket
from urllib.parse import urlparse
import requests
from requests.adapters import HTTPAdapter
from urllib3.poolmanager import PoolManager
ALLOWED_SCHEMES = {"http", "https"}
def is_blocked_ip(ip_str: str) -> bool:
ip = ipaddress.ip_address(ip_str)
return (
ip.is_private
or ip.is_loopback
or ip.is_link_local # 169.254.0.0/16, contains the metadata endpoint
or ip.is_multicast
or ip.is_reserved
or ip.is_unspecified
)
class PinnedIPAdapter(HTTPAdapter):
"""Forces TLS SNI and certificate validation against the original name."""
def __init__(self, hostname: str, **kwargs):
self._hostname = hostname
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False, **kw):
self.poolmanager = PoolManager(
num_pools=connections, maxsize=maxsize, block=block,
server_hostname=self._hostname, **kw
)
def safe_get(user_url: str, timeout: int = 5) -> requests.Response:
parsed = urlparse(user_url)
# 1. Restrict the scheme strictly
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError(f"Scheme not allowed: {parsed.scheme!r}")
hostname = parsed.hostname
if not hostname:
raise ValueError("No hostname in the URL")
# 2. Resolve once and check EVERY returned IP
infos = socket.getaddrinfo(hostname, parsed.port or 0, proto=socket.IPPROTO_TCP)
ips = {info[4][0] for info in infos}
for ip in ips:
if is_blocked_ip(ip):
raise ValueError(f"Target IP not allowed: {ip}")
# 3. Connect to exactly the validated IP, Host header keeps the original name
pinned_ip = next(iter(ips))
port = parsed.port or (443 if parsed.scheme == "https" else 80)
pinned_url = f"{parsed.scheme}://{pinned_ip}:{port}{parsed.path or '/'}"
if parsed.query:
pinned_url += "?" + parsed.query
session = requests.Session()
session.mount(f"{parsed.scheme}://{pinned_ip}", PinnedIPAdapter(hostname))
return session.get(
pinned_url, headers={"Host": hostname},
allow_redirects=False, timeout=timeout # 4. Do not follow redirects
)
Java and Spring: TOCTOU-safe
In Java, the InetAddress class handles much of the range checking. isLinkLocalAddress() covers the critical metadata range 169.254.0.0/16, and isSiteLocalAddress() covers the private RFC1918 networks. The validator resolves the name, checks every address, and returns the pinned IP for the subsequent connection:
import java.net.InetAddress;
import java.net.URI;
import java.util.Set;
import org.springframework.stereotype.Component;
@Component
public class SsrfGuard {
private static final Set<String> ALLOWED_SCHEMES = Set.of("http", "https");
private boolean isBlockedAddress(InetAddress addr) {
return addr.isLoopbackAddress() // 127.0.0.0/8, ::1
|| addr.isLinkLocalAddress() // 169.254.0.0/16 (IMDS!), fe80::/10
|| addr.isSiteLocalAddress() // 10/8, 172.16/12, 192.168/16
|| addr.isAnyLocalAddress() // 0.0.0.0, ::
|| addr.isMulticastAddress(); // 224.0.0.0/4, ff00::/8
}
/** Validates the URL and returns the validated IP to pin. */
public InetAddress validateAndPin(String userUrl) throws Exception {
URI uri = URI.create(userUrl);
String scheme = uri.getScheme();
if (scheme == null || !ALLOWED_SCHEMES.contains(scheme.toLowerCase())) {
throw new SecurityException("Scheme not allowed: " + scheme);
}
String host = uri.getHost();
if (host == null) {
throw new SecurityException("No hostname in the URL");
}
InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress a : addresses) {
if (isBlockedAddress(a)) {
throw new SecurityException("Target IP not allowed: " + a.getHostAddress());
}
}
return addresses[0]; // pinned, validated IP
}
}
You then build the actual request with a Socket to new InetSocketAddress(pinnedIp, port), setting the Host header and, for TLS, the original hostname as SNI, instead of handing the name back to the HTTP client for a fresh resolution. Here too, disabling automatic redirect following is essential.
Securing cloud metadata: IMDSv2, GCP and Azure
Because the metadata endpoint is the highest-value SSRF target in the cloud, the providers have built their own countermeasures. On AWS, that is IMDSv2.
AWS IMDSv2
On 19 November 2019, AWS introduced Instance Metadata Service Version 2 (IMDSv2) as a defense-in-depth measure. The idea: instead of a simple GET request, IMDSv2 first requires a PUT request that returns a session-bound token. That token must then be sent as a header on every GET metadata query. The official flow according to AWS:
TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
curl http://169.254.169.254/latest/meta-data/profile \
-H "X-aws-ec2-metadata-token: $TOKEN"
The protective effect against SSRF stems from the mandatory PUT: most SSRF vulnerabilities only let the attacker supply a URL that the server fetches via GET. They usually have no control over the HTTP method (PUT instead of GET) or over arbitrary request headers. That makes classic GET-based exploits against the metadata fall flat. In addition, the token response has a default TTL (hop limit) of 1, so it cannot be forwarded beyond the instance, a safeguard against misconfigured reverse proxies.
An honest caveat: IMDSv2 is no silver bullet. AWS itself describes it as a defense-in-depth mechanism against several related attack classes (open WAFs, open reverse proxies, SSRF, and open layer 3 firewalls). It reliably defends against the common, simple GET-only SSRF patterns. Enforcing IMDSv2 and fully disabling IMDSv1 closes precisely the gap that the Capital One case, discussed below, was publicly associated with.
GCP and Azure
Google Cloud and Azure take a similar approach: their metadata endpoints deliberately cannot be fetched with a plain GET. You reach GCP at http://metadata.google.internal/computeMetadata/v1/, but only with the header Metadata-Flavor: Google. Azure uses http://169.254.169.254/metadata/instance and requires the header Metadata: true plus an api-version parameter. Since simple SSRF vectors cannot set custom headers, this header requirement acts as a built-in first hurdle, comparable to the token principle of IMDSv2.
SSRF in the OWASP Top 10: 2021 vs 2025
One point that almost no other source works out cleanly is the edition-specific history of SSRF in the OWASP Top 10. It changed fundamentally between 2021 and 2025.
A10:2021, SSRF as its own category
In the 2021 edition, SSRF was a standalone category for the first time: A10:2021 Server-Side Request Forgery (SSRF). How it got there is notable. It was not formed from the quantitative test data, but explicitly added from the community survey (ranked first among the community-proposed additions). In other words, its measured frequency was comparatively low at an average incidence rate of 2.72 percent, but the security community rated its potential impact so highly that SSRF was included anyway. Exactly one CWE was mapped to the category: CWE-918. Behind it stood 385 reported CVEs and 9,503 occurrences.
A01:2025, SSRF folded into Broken Access Control
In the final 2025 edition (officially called the eighth installment of the OWASP Top 10), SSRF is no longer its own category. CWE-918 was consolidated into A01:2025 Broken Access Control (see our guide to Broken Access Control and IDOR) and is listed there, alongside CWE-200, CWE-201 and CWE-352, as one of the notable CWEs. A01:2025 is by far the largest umbrella category with 40 mapped CWEs, backed by 32,654 CVEs and over 1.8 million occurrences. Within it, SSRF is now just one of 40 sub-weaknesses.
| Attribute | A10:2021 (SSRF) | A01:2025 (Broken Access Control) |
|---|---|---|
| Status of SSRF | Own category | Consolidated into A01 (one of 40 CWEs) |
| Mapped CWEs | 1 (CWE-918) | 40 |
| Total CVEs | 385 | 32,654 |
| Total occurrences | 9,503 | 1,839,701 |
| Origin | Community survey | Data analysis plus community |
Why place it under Broken Access Control? In essence, SSRF can be understood as a form of missing access control at the network layer: the server makes requests to resources it should not have access to. This reading is a plausible technical interpretation, not a verbatim OWASP quote. The verifiable fact stands: anyone searching for SSRF in the OWASP Top 10 today finds it under A01, no longer under A10. For the broader context, see our overview of the OWASP Top 10.
SSRF vs CSRF: the dangerous name confusion
SSRF and CSRF sound almost identical and get confused constantly. Technically, they have little in common. The clean distinction hinges on a single question: who makes the malicious request?
- SSRF (Server-Side Request Forgery): the server makes the request. The attacker tricks the server-side application into sending a request to an internal target from its own privileged network position. It is about abusing the server's trust relationship.
- CSRF (Cross-Site Request Forgery): the victim's browser makes the request. The attacker tricks a logged-in user's browser into sending a state-changing request, complete with a valid session cookie, to an application. It is about abusing the user's authenticated session.
In short: in SSRF the confused deputy is the server, in CSRF it is the browser. SSRF aims inward into the network, CSRF aims at an authenticated action in the victim's name. For an in-depth treatment of the second case, see our article CSRF: Cross-Site Request Forgery Explained. SSRF is also related to XXE (XML External Entities): an XML parser that resolves external entities can serve as the entry point through which a SSRF-style request is then triggered by the server.
Real cases: Capital One and two enterprise CVEs
Capital One 2019, the best-known case, with a caveat
No SSRF article can skip the Capital One breach. In 2019, an attacker gained access via a misconfigured open-source web application firewall (ModSecurity) running on AWS. According to the technical analysis by Krebs on Security, which cites a source with direct knowledge of the investigation, the WAF could be induced to make a request to the EC2 metadata service and thereby obtain the temporary IAM credentials of the WAF's role. With those keys, the attacker listed and exfiltrated data from Capital One S3 buckets.
Here, factual care is warranted, the kind most write-ups lack. AWS stated in correspondence that SSRF was "not the primary factor" in the attack. The dominant SSRF narrative comes from external security experts who analyzed the investigative record, not from an AWS confirmation. Capital One should therefore be regarded as the best-known publicly discussed, SSRF-associated incident, whose exact causal chain (SSRF against IMDSv1 versus WAF misconfiguration as the primary factor) remains disputed between researchers and AWS.
The number of affected people also deserves transparency: reporting cites roughly 100 million US customers and 6 million Canadian customers, together the frequently quoted figure of about 106 million. That total is a sum, not a single officially confirmed number. What is documented and official is the regulatory consequence: on 6 August 2020, the US regulator OCC imposed an 80 million dollar fine for inadequate risk assessment before the cloud migration. And, fitting the timeline, AWS introduced IMDSv2 in November 2019, a few months after the breach became public.
CVE-2021-26855 (ProxyLogon)
ProxyLogon shows that SSRF is far more than a bug-bounty topic. CVE-2021-26855 is a critical, unauthenticated SSRF vulnerability in Microsoft Exchange Server. It lets a remote attacker send arbitrary HTTP requests while authenticating as the Exchange server itself. Chained with CVE-2021-27065, it leads to full remote code execution. Microsoft released security updates on 2 March 2021. The vulnerability was exploited en masse in the wild and appears in the CISA Known Exploited Vulnerabilities catalog.
CVE-2021-21973 (VMware vCenter)
CVE-2021-21973 is a SSRF vulnerability in the vSphere Client of VMware vCenter Server, caused by insufficient URL validation in a plugin. VMware rated it only "Moderate" with a CVSSv3 base score of 5.3. It nonetheless landed in the CISA KEV list, a good example that even SSRF flaws rated moderate get actively exploited. An unauthenticated attacker with network access to port 443 can use it to probe internal networks and extract information.
Frequently Asked Questions (FAQ)
What is SSRF (Server-Side Request Forgery) in simple terms?
In an SSRF attack, an attacker tricks a web application into making a request to a destination the attacker chooses. The application fetches a user-influenced URL without checking where it actually goes. Because the request originates from the server, the attacker borrows its inside view and reaches internal services, local files, or the cloud metadata endpoint with its access keys.
What is the difference between SSRF and CSRF?
The decisive difference is who makes the malicious request. In Server-Side Request Forgery (SSRF), the server makes the request from its own privileged network position, so it is about abusing the server's trust relationship. In CSRF, the victim's browser makes the request, complete with a valid session cookie, so it is about abusing the user's authenticated session. SSRF aims inward into the network, CSRF aims at an action in the victim's name.
What is blind SSRF?
In blind SSRF the attacker can trigger the request but does not get the response back. Detection relies on side effects such as out-of-band interactions, for example a DNS lookup or an HTTP request to an attacker-controlled domain. If that domain records an incoming lookup, it proves the server actually issued the request.
How do you prevent SSRF?
Effective SSRF prevention uses a positive allowlist instead of a deny list, permits only the http and https schemes, and does not follow redirects automatically. The decisive control is TOCTOU-safe validation: resolve the hostname once, check every returned IP, and connect to that exact validated IP. In the cloud you should additionally enforce IMDSv2 and disable IMDSv1.
Is SSRF still in the OWASP Top 10?
SSRF is no longer its own category as of 2025. It was listed as a standalone A10:2021 in the 2021 edition, but in the 2025 edition CWE-918 was folded into A01:2025 Broken Access Control. So anyone searching for SSRF in the OWASP Top 10 today finds it under A01, no longer under A10.
Conclusion and checklist
SSRF always grows from the same root: an application fetches a user-influenced URL without checking the actual destination. Because the request originates from the server, the attacker borrows its inside view and reaches internal services, local files, and, most dangerously, the cloud metadata endpoint with its access keys. Naive filters based on blocklists or regex fail against alternative IP notations, DNS rebinding, and redirects.
Effective protection consists of a few rules, applied consistently:
- Where possible, give the user no free URL input, only a choice among predefined targets.
- Otherwise, enforce a positive allowlist for scheme, port, and destination, not a deny list as the primary control.
- Allow only
httpandhttps, block all other schemes. - Validate in a TOCTOU-safe way: resolve once, check every IP, connect to that exact IP.
- Do not follow redirects automatically.
- In the cloud, enforce IMDSv2 and disable IMDSv1; the header requirement on GCP and Azure complements this.
- Add network segmentation and least privilege as defense in depth, so a successful attack achieves as little as possible.
Follow these principles and use the TOCTOU-safe code shown here as a template, and SSRF turns from one of the most underestimated into a manageable vulnerability. For the broader context of these vulnerability classes, see our overview of the OWASP Top 10.