# Gotenberg has an SSRF deny-list bypass in IsPublicIP via IPv6 6to4 / NAT64 / site-local prefixes

Gotenberg's IsPublicIP deny-list misses IPv6 6to4/NAT64/site-local prefixes, letting an attacker-controlled AAAA record SSRF the outbound client to cloud IMDS.

- Payout: Research
- Severity: high
- Program: Gotenberg
- Found by: yuui25
- Disclosed: 2026-05-29
- CVE: CVE-2026-45741
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-86m8-88fq-xfxp
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-86m8-88fq-xfxp
- Tags: area:cloud domain:cloud domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:go technique:incomplete-fix technique:source-review vuln:access-control vuln:ssrf

**Target:** Go outbound HTTP client IP validation (pkg/gotenberg/outbound.go)
**Root cause:** IsPublicIP relies on Go stdlib helpers (IsLoopback/IsPrivate/IsLinkLocalUnicast) plus addr.Unmap(), which only normalizes ::ffff:0:0/96; IPv6 prefixes that embed IPv4 (2002::/16, 64:ff9b::/96, 64:ff9b:1::/48, fec0::/10) fall through to 'public'.
**Steps:**
1. Point a DNS AAAA record at an IPv6 address wrapping 169.254.169.254, e.g. `2002:a9fe:a9fe::` (6to4) or `64:ff9b::a9fe:a9fe` (NAT64).
2. Submit that hostname to a Gotenberg route that fetches URLs (e.g. Chromium URL convert) on a deployment with `WithDenyPrivateIPs(true)` on a dual-stack/NAT64 host.
3. IsPublicIP returns true; kernel routes the tunneled address to 169.254.169.254.
4. Read the IMDS response back from the rendered PDF.
**How found:** Reporter audited the IsPublicIP deny-list against Go stdlib semantics, noticed Unmap() only covers IPv4-mapped addresses, and enumerated IPv6 prefixes that embed IPv4 (6to4, NAT64 well-known/local-use, deprecated site-local) as a variant of CVE-2026-44430.
**Exploitation:** Unauthenticated attacker controls a DNS AAAA record embedding the IMDS IPv4; Gotenberg's outbound client fetches it, and the Chromium URL convert route returns the full response as a PDF, yielding a full-read SSRF that leaks IAM credentials from AWS/GCP/Azure metadata.
**Escalation:** Full-read SSRF to IMDS enables IAM credential theft and cloud account compromise.
**Insight:** When auditing SSRF IP deny-lists, enumerate every IPv6 prefix that embeds an IPv4 address (2002::/16, 64:ff9b::/96, 64:ff9b:1::/48, fec0::/10) — stdlib IsPrivate/Unmap won't catch them, and dual-stack/NAT64 hosts route them to the embedded internal IPv4.

## Proof of concept (copied exactly from the original report)

**PoC build/run scripts detecting IsPublicIP bypass**

```
cd poc/
./build.sh   # docker build (~30s)
./run.sh     # docker run — exits with code 1 (bug detected)

Expected output: `IsPublicIP(2002:a9fe:a9fe::) = true` — the function returns true for 3 addresses that wrap 169.254.169.254 (AWS IMDS).
```

