CWE-918, Server-Side Request Forgery (SSRF), occurs when a web server receives a URL or similar request from an upstream component and retrieves its contents, but does not make sure the request goes to the expected destination. Any feature that fetches something for a user can be exposed: image import, webhooks, link previews, URL retrievers, PDF generators and AI tools that browse.
How it happens
The server acts on the user's behalf, from inside the network. By supplying URLs to unexpected hosts or ports, an attacker can make the request appear to come from the server. That can get past access controls such as firewalls that would block the attacker's direct requests. MITRE also notes the server can be used as a proxy.
# Vulnerable: fetches whatever URL the user gives
@app.route("/preview")
def preview():
url = request.args["url"]
r = requests.get(url, timeout=5)
return r.content
A request for an internal address, a local service, or a cloud metadata address returns data the user was never meant to see.
The observed CVEs include several recent ones linked to AI tooling:
- CVE-2026-33626 (details): SSRF in an LLM toolkit that accessed arbitrary URLs for images, exploited in the wild in April 2026 to conduct port scanning.
- CVE-2024-3095: SSRF in an LLM application development framework, where the URL retriever allowed connections to local addresses using a crafted Location header.
- CVE-2023-32786: a chain where an LLM integration framework had prompt injection that let an attacker force the service to retrieve data from an arbitrary URL, in effect providing SSRF.
- CVE-2021-26855: SSRF in a mail server, exploited in the wild according to CISA KEV.
The Location header case is worth noting. Checking the first URL is not enough if the client follows redirects to a destination you did not check. The prompt injection chain shows a second route: the attacker may not control a URL parameter directly, but may control text that makes the model choose the URL.
What an attacker gains
MITRE lists:
- Confidentiality: read application data.
- Integrity: execute unauthorized code or commands.
- Access control: bypass protection mechanisms, as the server is trusted by internal resources the attacker cannot reach directly.
The exploited examples show reconnaissance, with port scanning through the server, and the server used as a stepping stone to internal systems.
How to find it
MITRE notes SAST can find some instances by following data flow from sources of input to sinks. The sink here is any HTTP client call whose destination comes from input, including indirect sources such as stored values, webhook settings, and model output.
Add testing:
- List every feature that makes outbound requests on behalf of a user.
- Point each one at a controlled listener you own and confirm which requests arrive.
- Try internal and loopback addresses, unusual ports, and a redirect from an allowed host to a disallowed one.
- A dynamic scan of a running application can exercise these parameters automatically.
How to fix it
The MITRE record lists no formal mitigations for this entry. These are widely used controls that address the mechanism described above:
- Prefer an allowlist of permitted hosts or destinations over any blocklist.
- Resolve the hostname yourself, check the resulting address against rules that refuse loopback, private and link-local ranges, and connect to that address, so a name cannot change meaning between check and use.
- Turn off automatic redirects, or revalidate each hop.
- Restrict schemes to the ones you need, usually https only.
- Put the fetcher in a network segment with no route to internal services, and enforce it with egress rules.
- For AI tools that fetch URLs, treat model output as untrusted input and apply the same checks.
import ipaddress, socket, urllib.parse
ALLOWED_HOSTS = {"images.example.com"}
def safe_fetch(url):
parts = urllib.parse.urlparse(url)
if parts.scheme != "https" or parts.hostname not in ALLOWED_HOSTS:
raise ValueError("destination not allowed")
ip = ipaddress.ip_address(socket.gethostbyname(parts.hostname))
if ip.is_private or ip.is_loopback or ip.is_link_local:
raise ValueError("address not allowed")
return requests.get(url, timeout=5, allow_redirects=False)
This is a sketch. A production version should connect to the checked address directly to close the gap between resolving and connecting.
Try it yourself
The DAST demo shows a scanner probing a web application's parameters, the kind of testing that surfaces server-side fetch problems.
Click through it at your own pace, or open it full screen.
Quick check
Why can SSRF bypass a firewall?
The request comes from the server, which is trusted by internal resources. The attacker never has to reach them directly.
Why is checking only the first URL not enough?
CVE-2024-3095 used a crafted Location header, so a redirect led to a local address. Validate every hop or disable redirects.
Can SSRF come from a language model?
Yes. In CVE-2023-32786 prompt injection made the service retrieve data from an arbitrary URL. Treat model chosen URLs like user input.