Logo von nextlevels
DEEN
Request a project

Server-side request forgery (SSRF)

Last updated on

In short

Server-side request forgery (SSRF) is a vulnerability in which an attacker gets the server to fetch a URL they control. The server thus becomes a proxy: it reaches internal services, database admin interfaces or the cloud provider's metadata API, which would be unreachable from outside.

MITRE lists the vulnerability as CWE-918.

The CWE definition sums it up in one sentence: the web server receives a URL from an upstream component, retrieves its content and does not sufficiently ensure that the request goes to the expected destination. The problem is not the fetch itself. The problem is that the server fetches with its own network position and its own permissions.

It is precisely this position that makes SSRF so dangerous in cloud environments. A shop server sits in a private network and talks to Redis, Elasticsearch, the ERP connector and its cloud's metadata service. None of these has a password, because "internal" was long regarded as protection. SSRF removes this protection without breaking a single firewall rule.

How SSRF works

The starting point is always a function that loads a URL on the server side: an image import via link, a webhook test button, a link preview, a PDF renderer that loads external stylesheets. The URL comes from the client. If the server passes it on unchecked, the attacker decides where the request goes.

The obvious targets are internal addresses: http://localhost:6379 for Redis, http://10.0.0.5:9200 for Elasticsearch, an admin panel without authentication behind the load balancer. The server answers the request and, depending on the implementation, the response lands directly with the attacker, for example as a "preview" or as an imported file.

The most rewarding target in the cloud is the metadata API at 169.254.169.254. On AWS it returns temporary credentials for the IAM role assigned to the instance. Anyone who can retrieve them acts with the server's permissions towards S3, databases and all other services. That is why AWS introduced a session-based procedure with IMDSv2.

IMDSv2 first requires a PUT request that returns a token, and does not accept this PUT if an X-Forwarded-For header is set. By default, the response carries an IP hop limit of 1, so it does not get through any proxy. A simple GET via SSRF therefore comes to nothing. Anyone still allowing IMDSv1 gives away this protection.

DNS rebinding and redirect tricks

Naive checks fail because of two classics. With DNS rebinding, the application resolves the host name, sees a public IP and waves the request through. The actual HTTP client then resolves the name again, and this time the attacker's DNS answers with 127.0.0.1 or 169.254.169.254. There is a gap between the check and its use; MITRE calls this pattern CWE-367, time-of-check time-of-use.

With the redirect trick, the URL points to a harmless domain belonging to the attacker that forwards with a 302 to the internal address. If the application only checks the initial URL and the HTTP client follows redirects automatically, the check is worthless. Then there are parser tricks such as http://example.com\@evil.com and IP notations in decimal, octal or hex form that a blocklist regex does not recognise.

Classification in the OWASP Top 10

In the 2021 edition, SSRF had its own category, A10:2021, with a 2.72 per cent incidence rate and 385 mapped CVEs, included on the basis of the community survey rather than the data. In the 2025 OWASP Top 10, the category has disappeared. The document's introduction puts it briefly: SSRF has been rolled into broken access control.

That is consistent. On the page for A01:2025, CWE-918 now sits alongside CWE-200 and CWE-352 among the highlighted weaknesses, in a category with 40 CWEs, an average incidence rate of 3.74 per cent and 1,839,701 occurrences. The idea behind it: a server that lends its network position to other people's requests violates an access boundary. More on this under broken access control.

In practice, the move does not change the priority. SSRF continues to appear as a separate finding in pentest reports and advisories, and the countermeasures are specific. The fact that the category is no longer visible in the ranking must not lead to it being missing from checklists.

Real-world examples: Capital One and Shopware

The classic is the Capital One incident of 2019. According to the company statement, the access took place on 22 and 23 March 2019 and was discovered on 19 July 2019. Around 100 million people in the USA and 6 million in Canada were affected, including about 140,000 social security numbers and 80,000 account numbers.

Technically, the attack ran via a misconfigured web application firewall on AWS. Brian Krebs described at the time how the WAF was made, via SSRF, to forward requests to the metadata service, which handed out temporary credentials. The associated IAM role was allowed to list and read all buckets. SSRF was the door; oversized permissions turned it into a data leak. The perpetrator was convicted in June 2022 of wire fraud and computer intrusion.

The second case is closer to everyday shop life. On 25 August 2026, Shopware published two advisories on SSRF via DNS rebinding. GHSA-fgjq-45xv-rj8r concerns media import via URL: an authenticated admin with media permissions could bypass the IP check of the FileUrlValidator via DNS rebinding and make the server send requests to internal services; CVSS 6.3, classified as CWE-367 and CWE-918.

GHSA-rrc3-p9vx-5373 concerns the app system: webhooks and payment, tax, checkout and context gateways bypassed the same validator; CVSS 3.1. Both gaps are closed in 6.6.10.23 and 6.7.13.1. As a workaround, Shopware recommends restricting outgoing connections to what is necessary and blocking private ranges, loopback, link-local and cloud metadata addresses. The pattern applies to every webhook integration, not just Shopware.

What is remarkable is that Shopware already had the check. The security reference describes it: by default, Shopware validates on URL upload that the address points to a publicly accessible resource. The option shopware.media.enable_url_validation switches this check off, and shopware.media.enable_url_upload_feature switches off upload via URL completely. Anyone who deactivated the validation because an internal DAM could not be reached has reopened the gap themselves.

Typical places in shops and back ends

SSRF rarely arises in the core logic. It arises in convenience features that nobody classifies as an attack surface. The following overview shows where it typically appears in shop and back-end projects and which check applies there.

Typical SSRF entry points in shop and back-end systems
FunctionWhy vulnerableFirst countermeasure
Media upload via URLServer loads any URL, response is storedAllow-list, keep validation active, redirects off
Webhook tester / webhook targetsTarget URL comes from the admin or an appBlock private ranges, DNS pinning
Image proxy / thumbnail serviceResponse is returned 1:1 to the clientOnly content type image/*, no raw response
PDF renderer (headless Chrome, wkhtmltopdf)Loads images, fonts, iframes from HTMLRenderer in an isolated network, egress firewall
Link preview / oEmbedUser URL is fetched on the serverCheck the resolver, timeouts, no redirects
Import jobs (feeds, ERP connectors)Source URL configurable, runs with system permissionsFixed allow-list, egress only to known hosts

Headless set-ups multiply these places. A storefront back end that fetches product images from the CMS, prices from the ERP and reviews from a SaaS service has three URL sources that someone configures. Each of them is an SSRF candidate as soon as the configuration can be changed via the admin interface.

Countermeasures that hold

The OWASP SSRF Prevention Cheat Sheet distinguishes two cases. Case 1: the application only needs to talk to known destinations. Then a strict allow-list of host, port and scheme applies, and the URL is built from validated components instead of being taken over entirely from the user. This is the normal case for ERP connectors and payment providers.

Case 2: the application has to load arbitrary external URLs, for example for link previews. Then only the negative check remains, and it has to be clean: resolve all A and AAAA records, check every IP against private, loopback, link-local and metadata ranges, and bind the connection to the checked IP. This pinning closes the rebinding window. HTTP redirects are disabled in the client and, if necessary, followed manually with a fresh check.

At network level, this includes an egress firewall that restricts the web server's outgoing traffic to known destinations and blocks the metadata service for processes that do not need it. IMDSv2 as mandatory, no IMDSv1. Add a network segment for renderers and importers in which Redis and the database are not reachable at all. And cut the permissions of the instance role according to the principle of least privilege, so that stolen credentials are worth little.

One point is regularly underestimated: logging. OWASP recommends logging accepted and blocked flows on the firewall. A web server that suddenly contacts 169.254.169.254 is an unmistakable signal that nobody sees without logging.

Example: URL allow-list check in NestJS

In NestJS, SSRF typically arises where a controller accepts a URL from the request and fetches it via HttpService. The following check for case 1 enforces scheme, host and port, resolves the host name and rejects private addresses. The resolved IP is then pinned to the HTTP client via the lookup option.

import { BadRequestException, Injectable } from '@nestjs/common';
import { lookup } from 'node:dns/promises';
import ipaddr from 'ipaddr.js';

const ALLOWED_HOSTS = new Set(['cdn.example-partner.de', 'api.erp.example.com']);

@Injectable()
export class SafeUrlService {
  async validate(raw: string): Promise<{ url: URL; ip: string }> {
    const url = new URL(raw);
    if (url.protocol !== 'https:') throw new BadRequestException('Only https allowed');
    if (!ALLOWED_HOSTS.has(url.hostname)) throw new BadRequestException('Host not allowed');
    if (url.port && url.port !== '443') throw new BadRequestException('Port not allowed');

    // Resolve all addresses and check them against private ranges
    const records = await lookup(url.hostname, { all: true });
    for (const { address } of records) {
      const range = ipaddr.process(address).range();
      if (range !== 'unicast') throw new BadRequestException('Target not public');
    }
    // The checked IP is pinned later via the lookup option (no second DNS lookup)
    return { url, ip: records[0].address };
  }
}

The call itself then runs with maxRedirects: 0 and an agent whose lookup function returns the checked IP. Anyone who instead only checks url.hostname against a regex has recreated the two Shopware gaps.

Distinction

SSRF is often confused with CSRF, yet the directions are opposite. With cross-site request forgery, the attacker uses the browser of a logged-in victim to send requests to an application. With SSRF, they use the application's server to send requests to third parties. CSRF needs a victim with a session, SSRF needs a server with network access.

An open redirect is a redirect that an attacker can steer to a third-party domain, typically for phishing. The server does not fetch anything; it only responds with a Location header. However, open redirects become a tool for SSRF when a vulnerable fetch endpoint follows redirects.

XXE (XML external entity) is an injection into XML parsers in which external entities load files or URLs. XXE can result in SSRF because the parser loads resources on the server side. The cause, however, is a misconfigured parser, not an endpoint that accepts URLs. Both share the remedy at network level: egress firewall and segmentation.

Supply chain attacks lie one level further. A compromised app that registers webhooks via the app system brings its SSRF possibilities with it; see software supply chain attack. When building back ends with many integrations, egress is therefore an architectural decision, not a later fix; that is why in enterprise software development network boundaries are drawn first.

Frequently asked questions

Is it enough to block private IP ranges?

No. A blocklist fails because of DNS rebinding, redirects and alternative IP notations. It is only justifiable if the application really has to load arbitrary external URLs, and even then only with complete resolution of all records, pinning of the checked IP and disabled redirects. Where an allow-list is possible, it is the better choice.

Is SSRF harmless in an on-premise environment without cloud metadata?

Less spectacular, but not harmless. Without a metadata API, the direct route to cloud credentials is missing. Internal services such as Redis, Elasticsearch, Jenkins or an admin panel without a log-in are still reachable, and port scans via the server remain possible. The Shopware advisories of August 2026 explicitly name internal network services as targets, regardless of hosting.

How do I find SSRF in an existing Shopware or NestJS project?

Look for every code path that reads a URL from the request, database or admin configuration and fetches it: HttpService, fetch, axios, in Shopware the FileUrlValidator and anything that bypasses it. Then check whether validation is active, whether redirects are followed and whether the resolved IP is pinned. Add a pentest with your own DNS server that simulates rebinding, and log the web server's outgoing connections for a week. Whatever goes to internal addresses there belongs on the list.

Next step

How exposed is your website from the outside?

A quick external scan of headers, TLS and exposed endpoints shows what an attacker sees first when they look at your site.

Changelog

  1. · Created · Jan Schumann