GHSA-4vmc-gm8v-m35h: Gotenberg vulnerable to unauthenticated SSRF via default deny-list bypass in downloadFrom and webhook
SummaryThe default deny-lists used by Gotenberg's `downloadFrom` feature and `webhook` feature are bypassable. Because the filter is regex-based and case-sensitive, an unauthenticated attacker can supply URLs such as `http://[::ffff:127.0.0.1]:...` and reach loopback or private HTTP services that the default deny-list is intended to block. This crosses a real security boundary because an external caller can force the server to make outbound requests to internal-only targets.DetailsThe issue originates from the shipped default deny-list regexes and the way those regexes are applied:`pkg/modules/api/api.go:198-200` defines the default `api-download-from-deny-list`.`pkg/modules/webhook/webhook.go:41-43` defines the default `webhook-deny-list`.`pkg/gotenberg/filter.go:20-69` evaluates those patterns with `regexp2` using case-sensitive matching.The attacker-controlled URL then reaches outbound request sinks:`pkg/modules/api/context.go:208-282`Reads attacker-supplied `downloadFrom`.Calls `gotenberg.FilterDeadline(...)`.Issues an outbound GET with `retryablehttp.NewRequest(...)` and `client.Do(...)`.`pkg/modules/webhook/middleware.go:99-217`Reads `Gotenberg-Webhook-Url` and `Gotenberg-Webhook-Events-Url`.Calls `gotenberg.FilterDeadline(...)`.Constructs a `client` for outbound delivery.`pkg/modules/webhook/client.go:39-152`Sends the success or error webhook request.`pkg/modules/webhook/client.go:155-216`Sends the webhook event request.Why the bypass works:The default deny-list only blocks lowercase `http://` and `https://` prefixes.The filtering logic performs case-sensitive regex matching on the raw user input.Go's HTTP stack accepts multiple textual representations of loopback/private addresses that are not covered by the default regex, including IPv4-mapped IPv6 loopback like `http://[::ffff:127.0.0.1]:18081/...`.As a result, a URL can fail the deny-list check but still be interpreted as a valid loopback/private destination by the outbound client.Confirmed bypass used during verification:`http://[::ffff:127.0.0.1]:18081/page_1.pdf``http://[::ffff:127.0.0.1]:18082/upload``http://[::ffff:127.0.0.1]:18082/events`This is not the same issue as the previously published Chromium deny-list advisories. This finding affects the separate `downloadFrom` and `webhook` URL filtering paths.PoCOne-command verificationFrom the repository root:What the script does:Builds or reuses a slim local Gotenberg image that contains only the modules needed for this proof.Starts Gotenberg on `127.0.0.1:3000`.Starts an internal-only helper listener inside the same container network namespace.Verifies `downloadFrom` SSRF by forcing Gotenberg to fetch a PDF from `http://[::ffff:127.0.0.1]:18081/page_1.pdf`.Verifies `webhook` SSRF by forcing Gotenberg to POST to `http://[::ffff:127.0.0.1]:18082/upload` and `http://[::ffff:127.0.0.1]:18082/events`.Writes evidence artifacts to disk.Expected success output:Evidence files created by the script:`/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/downloadfrom-metadata.json``/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg/tmp/poc/artifacts/webhook.log`Manual evidence commandsThe following commands were run after the verifier completed successfully:Observed output:PoC Video:https://github.com/user-attachments/assets/a70a4e09-e9a7-4df8-a9a5-77b09fbd59f3Interpretation:The JSON metadata proves Gotenberg successfully fetched and parsed a PDF from an internal loopback URL.The webhook log proves Gotenberg sent outbound requests to internal loopback endpoints that should have been blocked by the default deny-list.`verify_ssrf_poc.sh`ImpactThis is an unauthenticated SSRF vulnerability. Any user who can reach a Gotenberg instance can coerce it into making outbound HTTP requests to loopback and potentially other private/internal addresses despite the default deny-list. That can expose internal HTTP services, cloud metadata endpoints, local admin APIs, and service-to-service interfaces that are not intended to be reachable from the public network.Affected users are operators who rely on the default `downloadFrom` and `webhook` deny-lists for SSRF protection. In practice, an attacker can:Read content from internal HTTP endpoints through `downloadFrom`.Trigger state-changing POST/PATCH/PUT requests through the `webhook` feature.Reach services bound only to localhost from the perspective of the Gotenberg host or container.RemediationNormalize and structurally validate URLs before any allow-list or deny-list decision.
Parse with `net/url`, lowercase the scheme/host where appropriate, canonicalize bracketed IPv6 forms, strip trailing dots, and normalize IPv4-mapped IPv6 addresses before evaluation.Replace regex-only private-address filtering with resolved IP validation.
Resolve the hostname, evaluate every resolved IP with `net/netip`, and block loopback, RFC1918, link-local, unspecified, ULA, multicast, and IPv4-mapped IPv6 private/loopback targets. Re-validate after redirects as well.Reconsider the security default for outbound URL features.
Either disable `downloadFrom` and `webhook` by default, or ship a strict default policy that only allows `http`/`https` plus explicit operator allow-lists. If the feature remains enabled, apply the same canonicalization and IP checks consistently to `downloadFrom`, `webhook`, error URLs, and event URLs.
CVSS v3.1: 9.4