CVE-2026-73087: Dozzle: SSRF guard bypass via IPv6 transition addresses (6to4/NAT64/Teredo) in webhook notification dispatcher

Published Aug 11, 2026
·
Updated

Summary

The isBlockedIP SSRF guard in Dozzle's webhook notification dispatcher blocks loopback, link-local, multicast, and unspecified addresses but does not recognize IPv6 transition mechanism addresses (RFC 3056 6to4, RFC 6052 NAT64, RFC 4380 Teredo) that embed arbitrary IPv4 addresses. An authenticated user can bypass the guard to reach loopback services, cloud metadata endpoints (169.254.169.254), and other blocked ranges via webhook notification URLs.

Affected component / versions

- Package: github.com/amir20/dozzle - Affected versions: all versions with SSRF guard (current HEAD b9df313) - Vulnerable code: internal/notification/dispatcher/webhook.go

Details

Root cause (CWE-918)

internal/notification/dispatcher/webhook.go:32-51:

go func isBlockedIP(ip net.IP) bool { if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsInterfaceLocalMulticast() || ip.IsUnspecified() { return true } if v4 := ip.To4(); v4 != nil && zeroNetV4.Contains(v4) { return true } if ip.Equal(net.IPv4bcast) { return true } return false }

The guard intentionally allows RFC 1918 private ranges for self-hosted webhook targets, but blocks loopback (127.0.0.0/8, ::1), link-local (169.254.0.0/16, fe80::/10), and other non-routable addresses. IPv6 transition mechanism addresses bypass all these checks:

| Mechanism | Prefix | Embeds | isBlockedIP result | |-----------|--------|--------|---------------------| | 6to4 | 2002::/16 | any IPv4 in bits 16-47 | false | | NAT64 WKP | 64:ff9b::/96 | any IPv4 in bits 96-127 | false | | Teredo | 2001:0000::/32 | any IPv4 in bits 96-127 | false |

Reachability / trust boundary

The safeDialContext function (line 53) resolves hostnames and checks each IP against isBlockedIP before establishing a TCP connection. This is used as the DialContext for the webhook HTTP client (line 115).

Webhook URLs are configured by authenticated Dozzle users through the notification settings UI. The guard exists to prevent authenticated users from using webhook delivery as a proxy to reach the host machine's loopback services or cloud metadata endpoint.

Attack chain

1. Authenticated user creates a webhook notification with URL http://[2002:7f00:0001::1]:8080/ (6to4 embedding 127.0.0.1) 2. When a notification triggers, Dozzle's webhook dispatcher calls safeDialContext 3. The IPv6 address 2002:7f00:0001::1 is checked against isBlockedIP -- all predicates return false 4. Connection proceeds to the 6to4 relay which routes to 127.0.0.1 5. The webhook POST reaches the host's loopback services

Impact

An authenticated user can bypass the SSRF guard to:

- Reach cloud metadata service at 169.254.169.254 via 2002:a9fe:a9fe::1 (6to4) to steal instance credentials - Reach localhost services via 64:ff9b::7f00:1 (NAT64) or 2002:7f00:0001::1 (6to4) - The webhook response body is logged at debug level but not returned to the user, making this a semi-blind SSRF (status code is returned)

Note: RFC 1918 private ranges are intentionally allowed by the guard. This bypass specifically targets the blocked ranges (loopback and link-local/metadata) that the guard explicitly intends to prevent.

Proof of concept

Bypass vectors:

6to4 embedding 127.0.0.1 (bypasses IsLoopback) http://[2002:7f00:0001::1]:8080/

NAT64 embedding 169.254.169.254 (bypasses IsLinkLocalUnicast) http://[64:ff9b::a9fe:a9fe]/latest/meta-data/

6to4 embedding 169.254.169.254 (bypasses IsLinkLocalUnicast) http://[2002:a9fe:a9fe::1]/latest/meta-data/

Teredo embedding 127.0.0.1 http://[2001:0000:dead:beef:0000:0000:7f00:0001]:8080/

Verification that isBlockedIP returns false for all vectors:

go 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

Remediation

Add IPv6 transition mechanism prefix checks to isBlockedIP:

go func isBlockedIP(ip net.IP) bool { // ... existing checks ...

// IPv6 transition mechanisms embedding arbitrary IPv4 if len(ip) == net.IPv6len { if ip[0] == 0x20 && ip[1] == 0x02 { return true } // 6to4 if ip[0] == 0x00 && ip[1] == 0x64 && ip[2] == 0xff && ip[3] == 0x9b { return true } // NAT64 if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && ip[3] == 0x00 { return true } // Teredo } return false }

Credit

Reported by tonghuaroot (tonghuaroot@gmail.com).

Other sources

Dozzle is a realtime log viewer for docker containers. From 10.5.2 until 10.6.15, the isBlockedIP SSRF guard in internal/notification/dispatcher/webhook.go, used by safeDialContext for webhook notification URLs, does not inspect IPv4 addresses embedded in 6to4, NAT64, Teredo, or IPv4-compatible IPv6 addresses, allowing an authenticated user to reach loopback or link-local targets that the guard intends to block. This issue is fixed in version 10.6.15.

— NVD

Affected Software

2 affected componentsFixes available
Dozzle Dozzle>10.5.2<=10.6.15
go/github.com/amir20/dozzle<1.29.1-0.20260804193351-8cf7ccd5ee04
1.29.1-0.20260804193351-8cf7ccd5ee04

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/amir20/dozzle to a version that resolves this vulnerability.

    Fixed in 1.29.1-0.20260804193351-8cf7ccd5ee04
  2. Upgrade

    Upgrade github.com/amir20/dozzle to a version that resolves this vulnerability.

    Fixed in 10.6.15
  3. Configuration

    Update the SSRF guard in internal/notification/dispatcher/webhook.go (used by safeDialContext) to recognize and block IPv6 transition mechanism addresses that embed arbitrary IPv4 (6to4 prefix 2002::/16, NAT64 WKP 64:ff9b::/96, Teredo prefix 2001:0000::/32). This is the specific code change described as the remediation, and it is included/fixed in version 10.6.15.

    Dozzle internal/notification/dispatcher/webhook.go isBlockedIP Add IPv6 transition mechanism prefix checks to isBlockedIP for 6to4/NAT64/Teredo embedded IPv4 = implemented

Event History

Aug 11, 2026
CVE Published
via MITRE·04:52 PM
Data Sourced
via MITRE·04:52 PM
DescriptionWeakness
Data Sourced
via NVD·05:19 PM
DescriptionSeverityWeakness
Sep 8, 2026
Advisory Published
via GitHub·06:11 PM
Data Sourced
via GitHub·06:11 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

What is the severity of CVE-2026-73087?

CVE-2026-73087 has a severity score of 47, indicating a notable risk level.

2

How do I fix CVE-2026-73087?

To mitigate CVE-2026-73087, upgrade Dozzle to version 10.6.16 or later where the SSRF guard has been improved.

3

What is the impact of CVE-2026-73087?

CVE-2026-73087 allows attackers to bypass the SSRF guard by using IPv4 addresses embedded in IPv6 formats for webhook notifications.

4

Which software is affected by CVE-2026-73087?

CVE-2026-73087 affects Dozzle versions from 10.5.2 up to 10.6.15.

5

What type of vulnerability is CVE-2026-73087 classified as?

CVE-2026-73087 is classified as a Server-Side Request Forgery (SSRF) vulnerability.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203