GitHub Security Advisory: GHSA-4vmc-gm8v-m35h
Package: github.com/gotenberg/gotenberg/v8 (GO)
Severity: CRITICAL (CVSS 9.4)
CWE: CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: <= 8.30.1
Patched in: 8.32.0

Summary: Gotenberg vulnerable to unauthenticated SSRF via default deny-list bypass in downloadFrom and webhook

Details:
### Summary
The 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.

### Details
The 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:

1. The default deny-list only blocks lowercase `http://` and `https://` prefixes.
2. The filtering logic performs case-sensitive regex matching on the raw user input.
3. 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/...`.
4. 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.

### PoC
#### One-command verification
From the repository root:

```bash
cd '/Users/r1zzg0d/Documents/CVE hunting/targets/gotenberg'
./tmp/poc/verify_ssrf_poc.sh
```

What the script does:

1. Builds or reuses a slim local Gotenberg image that contains only the modules needed for this proof.
2. Starts Gotenberg on `127.0.0.1:3000`.
3. Starts an internal-only helper listener inside the same container network namespace.
4. Verifies `downloadFrom` SSRF by forcing Gotenberg to fetch a PDF from `http://[::ffff:127.0.0.1]:18081/page_1.pdf`.
5. Verifies `webhook` SSRF by forcin