{"id":"CVE-2026-45741","aliases":["GHSA-86m8-88fq-xfxp","GO-2026-5244"],"url":"https://o3.security/vulnerability/CVE-2026-45741","summary":"Gotenberg: SSRF deny-list bypass in IsPublicIP via IPv6 6to4 / NAT64 / site-local prefixes","details":"### Summary\n\n`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).\n\n### Details\n\n`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:\n\n| Prefix | RFC | Tunnels to |\n|--------|-----|-----------|\n| `2002::/16` | RFC 3056 (6to4) | IPv4 in bits 16-47 |\n| `64:ff9b::/96` | RFC 6052 (NAT64 well-known) | IPv4 in low 32 bits |\n| `64:ff9b:1::/48` | RFC 8215 (NAT64 local-use) | IPv4 in low 32 bits |\n| `fec0::/10` | RFC 3879 (deprecated site-local) | internal routing |\n\n`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.\n\nVulnerable code (`pkg/gotenberg/outbound.go` L53-69, commit `93d0103`):\n\n```go\nfunc IsPublicIP(addr netip.Addr) bool {\n    addr = addr.Unmap() // only handles ::ffff:x.x.x.x\n    switch {\n    case addr.IsLoopback(), addr.IsPrivate(),\n         addr.IsLinkLocalUnicast(), ...:\n        return false\n    }\n    return true // 6to4/NAT64/site-local incorrectly reaches here\n}\n```\n\n### PoC\n```\ncd poc/\n./build.sh   # docker build (~30s)\n./run.sh     # docker run — exits with code 1 (bug detected)\n```\n\nExpected 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.\n\n### Impact\n\nAn 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.\n\n### Suggested Fix\n\nAdd explicit prefix checks after `addr.Unmap()`:\n```\nvar blockedIPv6Prefixes = []netip.Prefix{\n    netip.MustParsePrefix(\"2002::/16\"),\n    netip.MustParsePrefix(\"64:ff9b::/96\"),\n    netip.MustParsePrefix(\"64:ff9b:1::/48\"),\n    netip.MustParsePrefix(\"fec0::/10\"),\n}\nfor _, p := range blockedIPv6Prefixes {\n    if p.Contains(addr) { return false }\n}\n```","published":"2026-08-19T14:32:50.229Z","modified":"2026-09-11T03:48:23.924204336Z","cvss":{"score":7.5,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:L/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"Go","name":"github.com/gotenberg/gotenberg/v8","fixedVersion":null}],"fix":{"url":"https://github.com/gotenberg/gotenberg/commit/f9a01c9fb308372170ab7fc97b17a118ee8a125c","label":"gotenberg/gotenberg@f9a01c9"},"references":[{"type":"WEB","url":"https://github.com/gotenberg/gotenberg/releases/tag/v8.33.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/45xxx/CVE-2026-45741.json"},{"type":"ADVISORY","url":"https://github.com/gotenberg/gotenberg/security/advisories/GHSA-86m8-88fq-xfxp"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-45741"},{"type":"FIX","url":"https://github.com/gotenberg/gotenberg/commit/f9a01c9fb308372170ab7fc97b17a118ee8a125c"},{"type":"PACKAGE","url":"https://github.com/gotenberg/gotenberg"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-09-11T03:48:23.924204336Z"}}