Gotenberg has an SSRF deny-list bypass in IsPublicIP via IPv6 6to4 / NAT64 / site-local prefixes
Description
Summary
IsPublicIP in pkg/gotenberg/outbound.go incorrectly classifies IPv6 6to4 / NAT64 / deprecated site-local addresses as public IPs, allowing an unauthenticated attacker to reach internal destinations (e.g., cloud metadata services at 169.254.169.254) via a single crafted DNS AAAA record. This is a variant of CVE-2026-44430 (modelcontextprotocol/registry).
Details
IsPublicIP uses Go stdlib helpers (IsLoopback, IsPrivate, IsLinkLocalUnicast, etc.) to block internal IPs. However, these helpers do not recognize IPv6 prefixes that embed IPv4 addresses:
| Prefix | RFC | Tunnels to | |--------|-----|-----------| | 2002::/16 | RFC 3056 (6to4) | IPv4 in bits 16-47 | | 64:ff9b::/96 | RFC 6052 (NAT64 well-known) | IPv4 in low 32 bits | | 64:ff9b:1::/48 | RFC 8215 (NAT64 local-use) | IPv4 in low 32 bits | | fec0::/10 | RFC 3879 (deprecated site-local) | internal routing |
addr.Unmap() only handles ::ffff:0:0/96 (IPv4-mapped) and has no effect on these prefixes. On dual-stack or NAT64-enabled cloud hosts, the OS kernel transparently routes these addresses to their embedded internal IPv4 destinations.
Vulnerable code (pkg/gotenberg/outbound.go L53-69, commit 93d0103):
func IsPublicIP(addr netip.Addr) bool {
addr = addr.Unmap() // only handles ::ffff:x.x.x.x
switch {
case addr.IsLoopback(), addr.IsPrivate(),
addr.IsLinkLocalUnicast(), ...:
return false
}
return true // 6to4/NAT64/site-local incorrectly reaches here
}
PoC
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). Full test file available via GHSA private comment on request.
Impact
An unauthenticated attacker controlling a DNS AAAA record can tunnel gotenberg's outbound HTTP client to AWS/GCP/Azure IMDS (169.254.169.254), leaking IAM credentials. The Chromium URL convert route returns the full response as a PDF (full-read SSRF). Affects all deployments with WithDenyPrivateIPs(true) on dual-stack or NAT64-enabled hosts.
Suggested
Fix
Add explicit prefix checks after addr.Unmap(): `` var blockedIPv6Prefixes = []netip.Prefix{ netip.MustParsePrefix("2002::/16"), netip.MustParsePrefix("64:ff9b::/96"), netip.MustParsePrefix("64:ff9b:1::/48"), netip.MustParsePrefix("fec0::/10"), } for _, p := range blockedIPv6Prefixes { if p.Contains(addr) { return false } } ``
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
IsPublicIP in Gotenberg incorrectly classifies IPv6 6to4/NAT64/site-local prefixes as public, allowing SSRF to cloud metadata services via a crafted DNS AAAA record.
Vulnerability
The IsPublicIP function in pkg/gotenberg/outbound.go (commit 93d0103, lines 53–69) fails to block IPv6 addresses with embedded IPv4 destinations. The Go stdlib helpers (IsLoopback, IsPrivate, IsLinkLocalUnicast) do not recognize prefixes such as 2002::/16 (6to4, RFC 3056), 64:ff9b::/96 and 64:ff9b:1::/48 (NAT64, RFC 6052/8215), or fec0::/10 (deprecated site-local, RFC 3879). The addr.Unmap() call only handles ::ffff:0:0/96 and is ineffective for these prefixes. As a result, addresses that wrap internal IPv4 targets (e.g., 2002:a9fe:a9fe:: for 169.254.169.254) are incorrectly classified as public. All deployments using WithDenyPrivateIPs(true) on dual-stack or NAT64-enabled hosts are affected [1][2].
Exploitation
An unauthenticated attacker who can control a DNS AAAA record for a domain that Gotenberg resolves can craft a record pointing to one of the vulnerable IPv6 prefixes. When Gotenberg's outbound HTTP client (used by the Chromium URL convert route) resolves the domain, the OS kernel transparently routes the embedded IPv4 address to the internal destination. No authentication or special network position is required beyond being able to serve a DNS record [1][2].
Impact
Successful exploitation allows the attacker to reach internal cloud metadata services (e.g., AWS/GCP/Azure IMDS at 169.254.169.254) and retrieve IAM credentials. Because the Chromium URL convert route returns the full HTTP response as a PDF, this is a full-read SSRF. The attacker gains access to the cloud provider's metadata API, potentially leading to credential leakage and further compromise of the cloud environment [1][2].
Mitigation
A fix is not yet disclosed in the available references. Users should monitor the Gotenberg repository and GitHub advisory for a patched release [1][2]. As a workaround, administrators can disable the WithDenyPrivateIPs feature or restrict outbound DNS resolution to prevent AAAA record injection, though this may reduce functionality. The vulnerability is a variant of CVE-2026-44430 and is tracked in GHSA-86m8-88fq-xfxp [1][2].
AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
0No patches discovered yet.
Vulnerability mechanics
Root cause
"Missing explicit prefix checks for IPv6 6to4, NAT64, and deprecated site-local address ranges in `IsPublicIP` allows addresses embedding internal IPv4 destinations to bypass the private-IP deny list."
Attack vector
An unauthenticated attacker who controls a DNS AAAA record can craft an IPv6 address in the `2002::/16` (6to4), `64:ff9b::/96` or `64:ff9b:1::/48` (NAT64), or `fec0::/10` (site-local) ranges that embeds an internal IPv4 destination such as `169.254.169.254` (cloud metadata service) [ref_id=1]. The `IsPublicIP` function incorrectly returns `true` for these addresses, so the outbound HTTP client proceeds to connect. On dual-stack or NAT64-enabled cloud hosts, the OS kernel transparently routes the connection to the embedded internal IPv4 address, enabling full-read SSRF via the Chromium URL convert route which returns the response as a PDF [ref_id=1].
Affected code
The vulnerable function is `IsPublicIP` in `pkg/gotenberg/outbound.go` at lines 53–69 (commit `93d0103`) [ref_id=1]. It calls `addr.Unmap()` which only handles the IPv4-mapped prefix `::ffff:0:0/96`, then relies on Go stdlib helpers (`IsLoopback`, `IsPrivate`, `IsLinkLocalUnicast`) that do not recognize IPv6 6to4, NAT64, or deprecated site-local prefixes [ref_id=1].
What the fix does
The advisory recommends adding explicit prefix checks after `addr.Unmap()` for `2002::/16`, `64:ff9b::/96`, `64:ff9b:1::/48`, and `fec0::/10` [ref_id=1]. These checks ensure that IPv6 addresses embedding internal IPv4 destinations via 6to4, NAT64, or deprecated site-local mechanisms are classified as non-public and blocked, closing the SSRF bypass [ref_id=1]. No patch commit is provided in the bundle; the advisory text serves as the remediation guidance.
Preconditions
- configGotenberg must be configured with WithDenyPrivateIPs(true)
- configHost must be dual-stack or NAT64-enabled for kernel routing of embedded IPv4 addresses
- inputAttacker must control a DNS AAAA record returned to gotenberg's outbound HTTP client
Reproduction
```bash 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) [ref_id=1].
Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.