Plunk SSRF via Unvalidated AWS SNS SubscriptionConfirmation — CVE-2026-32096
by Andre Hu (andrebhu) · 2026-07-05
- Severity
- Critical
- CVE
- CVE-2026-32096
- Category
- cloud
- Affected product
- Plunk (useplunk/plunk) email/webhook API
- Affected versions
- Versions with unpatched apps/api/src/controllers/Webhooks.ts POST /webhooks/sns handler
- Disclosed
- 2026-07-05
- Patch status
- unpatched
References
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-03 |
| Author / Researcher | Andre Hu (andrebhu) |
| CVE / Advisory | CVE-2026-32096 |
| Category | cloud |
| Severity | Critical |
| CVSS Score | 9.3 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N) |
| Status | PoC |
| Tags | ssrf, aws-sns, imds, cloud-metadata, plunk, cwe-918, unauthenticated |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Plunk (useplunk/plunk) email/webhook API |
| Versions Affected | Versions with unpatched apps/api/src/controllers/Webhooks.ts POST /webhooks/sns handler |
| Language / Platform | TypeScript/Node.js target; Bash + Python 3 PoC tooling |
| Authentication Required | No |
| Network Access Required | Yes |
Summary
Plunk’s POST /webhooks/sns endpoint is meant to handle AWS SNS subscription confirmation callbacks, but it fetches the attacker-supplied SubscribeURL field directly via fetch() without ever verifying the AWS SNS message signature. Because the endpoint requires no authentication and accepts arbitrary JSON, an attacker can forge a SubscriptionConfirmation message pointing SubscribeURL at any host, forcing the Plunk server to issue an outbound HTTP request on the attacker’s behalf. This is a classic full SSRF that can be used to reach cloud instance metadata services, internal-only APIs, or other non-internet-exposed infrastructure.
Vulnerability Details
Root Cause
Webhooks.ts calls await fetch(req.body.SubscribeURL) whenever req.body.Type === 'SubscriptionConfirmation', with no validation that the request actually originated from AWS SNS (no signature check) and no allowlisting of destination hosts (CWE-918).
Attack Vector
- Attacker sends an unauthenticated
POST /webhooks/snsrequest withType: "SubscriptionConfirmation"and a forgedSubscribeURLpointing at an attacker-controlled listener or an internal/cloud-metadata address. - The Plunk server calls
fetch(SubscribeURL)server-side with no signature or destination validation. - If the attacker’s listener is the target, it receives a confirmation hit proving SSRF; if the target is
http://169.254.169.254/latest/meta-data/iam/security-credentials/, the response (in a real AWS-hosted deployment) leaks IAM role names and can be chained to steal temporary credentials. - Repeat against internal hostnames/ports to enumerate reachable internal services.
Impact
Full-response SSRF allowing theft of cloud IAM credentials via IMDS, access to internal-only services (databases, Redis, Kubernetes API), and internal network port scanning — all pre-authentication.
Environment / Lab Setup
Target: useplunk/plunk API (Postgres + Redis + Plunk containers via docker-compose)
Attacker: Docker & Docker Compose, Python 3, bash, curl
Proof of Concept
PoC Script
See
exploit.sh,listener.py, anddocker-compose.ymlin this folder.
| |
docker-compose.yml stands up a local Plunk instance; listener.py runs an SSRF callback catcher; exploit.sh POSTs a forged SubscriptionConfirmation payload to /webhooks/sns and also fires a bonus request targeting the EC2 IMDS path to illustrate real-world impact.
Detection & Indicators of Compromise
POST /webhooks/sns {"Type":"SubscriptionConfirmation","SubscribeURL":"http://<non-aws-host>/..."}
Signs of compromise:
- Server-originated outbound HTTP requests to non-AWS, attacker-controlled, or internal-only hosts shortly after a
/webhooks/snscall. SubscribeURLvalues in webhook payloads that do not match*.amazonaws.comSNS confirmation URL patterns.- Anomalous IMDS (
169.254.169.254) traffic originating from the application server process.
Remediation
| Action | Detail |
|---|---|
| Primary fix | No vendor patch confirmed as of 2026-07-05 — monitor for advisory; validate the AWS SNS message signature before fetching SubscribeURL |
| Interim mitigation | Restrict outbound egress from the API host (block IMDSv1/require IMDSv2 with hop-limit, deny access to internal RFC1918 ranges), and allowlist SubscribeURL to *.amazonaws.com hosts only. |
References
Notes
Mirrored from https://github.com/andrebhu/CVE-2026-32096 on 2026-07-05.
| |