Safeguard
Application Security•

CWE-918 Server-Side Request Forgery (SSRF): Causes and Fixes

SSRF makes your server fetch URLs an attacker picks. CWE-918 covers how it happens, the AI toolkit and mail server CVEs, and the fixes that hold up.

Safeguard Security Team
5 min read

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:

  1. List every feature that makes outbound requests on behalf of a user.
  2. Point each one at a controlled listener you own and confirm which requests arrive.
  3. Try internal and loopback addresses, unusual ports, and a redirect from an allowed host to a disallowed one.
  4. 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:

  1. Prefer an allowlist of permitted hosts or destinations over any blocklist.
  2. 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.
  3. Turn off automatic redirects, or revalidate each hop.
  4. Restrict schemes to the ones you need, usually https only.
  5. Put the fetcher in a network segment with no route to internal services, and enforce it with egress rules.
  6. 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.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.