Two related missing authorization checks in the webhook subsystem combine to produce a high-severity SSRF vulnerability:
POST /webhook/config/save does not verify that sourceId (the form key) belongs to the calling user. Any authenticated user can associate an attacker-controlled webhook URL with any other user's form.
POST /webhook/config/sendTest does not validate the URL in the submitted WebhookConfigEntity at all, and uses the caller-supplied URL directly to make an outbound HTTP request (via WebhookSenderService.sendHttp() ), with no SSRF protections (no IP allowlist, no DNS rebinding protection, no scheme restriction).
When a new submission arrives on UserA's form, the server POSTs the full JSON payload -- including all submitted PII -- to the attacker-registered URL, exfiltrating data and allowing the attacker to port-scan and interact with internal services.
Vulnerable endpoint 1 -- webhook save ( tduck-webhook/src/main/java/com/tduck/cloud/webhook/controller/WebhookConfigController.java , lines ~38-43):
Vulnerable endpoint 2 -- test send ( WebhookConfigController.java , lines ~48-56):
HTTP sender with no SSRF guard ( tduck-webhook/src/main/java/com/tduck/cloud/webhook/service/sender/WebhookSenderService.java , lines ~66-85):
The PII payload sent to the attacker URL contains the complete serialized form submission including all field values, IP address, timestamps, and user metadata.
A running TDuck instance.
UserA (form owner) has a published form with formKey FORM_KEY_A .
UserB: any other registered user account.
Attacker controls a server at .
Step 1 -- UserB registers attacker URL as webhook for UserA's form (no ownership check):
Step 2 -- Any submission to UserA's form triggers the server to POST the full data to attacker URL:
Step 3 -- Attacker receives (on ATTACKER_IP:PORT):
Observed output from live validation (server logs from tduckcloud/tduck-platform docker):
The ConnectException: Connection refused error on port 29876 confirms the server made a real TCP dial to the attacker-controlled host. The PII payload in event_data confirms what would have been sent in the body.
Internal network scanning: By providing url values pointing to internal RFC-1918 addresses (e.g., , ) and observing whether the webhook event's last_error contains Connection refused (port closed) vs. ConnectException: Connection timed out or success, an attacker can map internal services. In a cloud/Kubernetes deployment this enables metadata service access (e.g., ).
An attacker with any authenticated account can:
Exfiltrate all future PII submissions to any form in the system -- names, emails, medical data, HR responses -- by registering their webhook endpoint for the target form.
Port-scan internal network : the error response distinguishes open, closed, and filtered ports on internal hosts.
Interact with internal services : send arbitrary POST bodies to internal HTTP endpoints (Redis, Elasticsearch, microservices) using the submission data as the body.
No administrative privileges are required. Self-registration is sufficient if enabled.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
