API SSRF: Allowlists Fail Because They Validate the URL, Not the Resolved IP
The Omise incident (HackerOne #508459) was a webhook. The US DoD finding (HackerOne #1624140) was a query parameter. Both resulted in stolen AWS credentials within minutes. Both had allowlists configured and active. Neither worked.
The standard defense against SSRF is a host allowlist. The problem is not that teams forget to create allowlists. The allowlist validates the URL string at parse-time; the HTTP client targets the IP resolved at request-time. These two moments are separable. Attackers exploit this window in under 60 seconds. Every SSRF chain that reached cloud credentials in the past three years depended on exactly this gap.
Why Allowlists Fail: Parse-Time Validation vs Request-Time Resolution
The allowlist sees https://webhook.allowed-partner.com. The HTTP client sees 169.254.169.254. The interval between validation and request is the vector. OWASP API Security (API7:2023) recommends allowlists without explaining this gap anywhere. PortSwigger 2024 acknowledges that the only reliable defense is to resolve the URL to the final IP after following all redirects. Only then validate the destination. This recommendation has existed for years. Almost no implementation follows it.
DNS rebinding exploits this interval with TTL=0. The attacker controls the DNS for evil.com. During allowlist validation, evil.com resolves to 203.0.113.50, a legitimate public IP. The request passes. On the second resolution, the HTTP client opens the TCP connection. The record expired with TTL zero; DNS returns 169.254.169.254. The OS cache does not help because TTL zero explicitly instructs clients not to cache.
Redirect chains are the second bypass, operationally simpler because they require no timing. HackerOne #508459 (Omise, $700 bounty, 193 upvotes) documented the pattern. The webhook URL pointed to a legitimate server and passed the allowlist. That server returned HTTP 303 to http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-opsworks-ec2-role. The HTTP client followed the redirect. The redirect destination was never re-validated against the allowlist. The private keys for IAM role aws-opsworks-ec2-role were exposed. CVE-2025-62155 (new-api) documents the same pattern four years later: fix applied to the initial URL, 302 redirect bypasses the fix and reaches the intranet.
Allowlist validates strings. The HTTP client follows IPs.
CVE-2024-29736: The SSRF Surface Web Guides Never Mention
WADL (Web Application Description Language) is the REST equivalent of WSDL: an XML document describing a service's endpoints, parameters, and return types. Apache CXF accepts a stylesheet parameter via query string in WADL description requests. The framework fetches that URL server-side, without authentication, without destination validation. CVE-2024-29736 carries CVSS 9.1 and affects the cxf-rt-rs-service-description component in versions before 4.0.5, 3.6.4, and 3.5.9.
The minimal request to trigger the SSRF:
GET /service?_wadl&stylesheet=http://169.254.169.254/latest/meta-data/ HTTP/1.1
Host: api.target.com
No authentication. No body. No active user required. The attacker sends the request and the server fetches the content of any URL provided in the stylesheet parameter. This surface does not exist in traditional web applications: it is specific to APIs with service description enabled in the framework. No PortSwigger lab covers WADL. No API security checklist I found mentions the _wadl parameter. Security teams tested conventional surfaces while CXF-based APIs stayed exposed.
The pattern repeats in OpenAPI tooling. swagger-typescript-api (CVE-2026-54663) traverses every $ref in an OpenAPI spec and issues a GET to each referenced URL. No private IP allowlist, no DNS rebinding protection, no redirect limit. A malicious spec reaches 169.254.169.254 from the build process itself, without any interaction with the production environment.
DNS Rebinding: The Race Condition Every Cloud SSRF Depends On
The attack has two steps with TTL zero. Step 1: the allowlist resolves evil.com to 203.0.113.50, a legitimate public IP controlled by the attacker. Validation passes. Step 2: the HTTP client resolves evil.com again when opening the TCP connection. TTL zero ensured the cache expired between the two steps. The attacker's DNS server now returns 169.254.169.254.
CVE-2024-22243 in Spring Framework documents exactly this pattern with EPSS at the 98th percentile. UriComponentsBuilder validated the host when constructing the URI object, at parse-time. The HTTP client resolved DNS again at request-time. APIs that accepted external URLs via query parameters and processed them with UriComponentsBuilder were vulnerable without any additional bypass code. The vulnerability was in the architectural gap between parse and execution.
AWS IMDSv2 requires a PUT with the X-aws-ec2-metadata-token-ttl-seconds header before any GET to the metadata endpoint. This blocks SSRF via pure GET against 169.254.169.254. It does not block SSRF to other services on the private network. It does not block SSRF in clients capable of issuing PUT requests, such as XML parsers, PDF generators, or any general-purpose HTTP client. GCP has an analogous requirement via the Metadata-Flavor: Google header, bypassable if the SSRF allows custom header injection. IMDSv2 is damage containment for the specific case of EC2 instance metadata, not a fix for the root problem.
SSRF Surfaces in APIs That Do Not Appear to Accept URLs
HackerOne #1624140 (US DoD) involved a parameter that looked like a content identifier, with no indication in the documentation that it would trigger an external HTTP request. No authentication. No documentation that the parameter accepted URLs. SSRF was confirmed via DNS callback to the AWS metadata endpoint. Without OOB (out-of-band), scanners testing only reflected responses would detect nothing.
HackerOne #3165242 (Lichess) came from the players parameter in the public game export API. The name suggests no URL. The endpoint is public and unauthenticated. Confirmation was entirely via DNS callback. HackerOne #1057531 (Automattic) documented GET /api/v2/url_info, a public endpoint that proxied arbitrary URLs to internal services without any response reflected to the user.
The pattern is consistent: blind SSRF lives in parameters not documented as URL inputs. Scanners that test only parameters named url, webhook, or redirect miss this entire vulnerability class. Reliable coverage requires DNS callback on every string parameter of every endpoint. SSRF attacks grew 452% between 2023 and 2024, according to F5 Labs, driven by automated tools scanning API parameters that consume URLs at scale.
How to Prove SSRF Without Burp Collaborator
interactsh is the open source equivalent of Burp Collaborator, operated by the ProjectDiscovery team. To register a unique subdomain:
curl -s https://interactsh.com/register
The command returns a unique ID, such as abc123.interactsh.com. For each candidate parameter, replace the value with http://abc123.interactsh.com/ssrf-test and submit the request. Check the interactsh dashboard in real time. A DNS callback confirms SSRF even without a reflected response: the server resolved the controlled domain, proving server-side HTTP request execution. An HTTP callback confirms further: the server established a TCP connection and sent the GET.
To confirm blind SSRF without any external tool, use latency difference. Compare response time with http://127.0.0.1:80 (fast failure, loopback without routing) against http://192.168.0.1:80 (slow, RFC 1918 routing attempting to reach a host with no guaranteed response). A significant latency difference indicates the server is attempting a TCP connection to an internal network address. This method does not replace OOB, but confirms the connection attempt when no external tool is available.
Which Parameter to Test First: Stack Fingerprint as Selector
Apache CXF APIs: test _wadl&stylesheet= immediately, before any other parameter. Spring Boot APIs: look for any endpoint that accepts a URL and executes redirect or fetch via UriComponentsBuilder. Webhook registration endpoints: always test redirect chains, pointing the webhook to your server and configuring it to return 301/303 to http://169.254.169.254/latest/meta-data/.
The tech_detector spell at intel.mago.team identifies the API framework (CXF, Spring, FastAPI) and flags surfaces known to be SSRF-prone before manual testing (MAGO team tool). Stack fingerprinting reduces the search space before any test request.
The question is not whether an allowlist exists. The real question is whether it validates the URL string at parse-time or the resolved IP at request-time. Without knowing the answer, assume parse-time. And assume it is already bypassable.
Top comments (0)