From 5d787969c2d5ab591a9dcd61b0810475eed7a646 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Thu, 29 Jan 2026 09:36:47 -0400 Subject: [PATCH] MM-67268: Fix SSRF bypass via IPv4-mapped IPv6 literals (#35097) Canonicalize IPv4-mapped IPv6 addresses (e.g., ::ffff:127.0.0.1) to their native IPv4 form in IsReservedIP before checking against reserved IP ranges. This prevents attackers from bypassing SSRF protections by using IPv4-mapped IPv6 literals to access internal services. --- server/public/shared/httpservice/client.go | 5 +++++ server/public/shared/httpservice/client_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/server/public/shared/httpservice/client.go b/server/public/shared/httpservice/client.go index b483b4c342d..954af74bb79 100644 --- a/server/public/shared/httpservice/client.go +++ b/server/public/shared/httpservice/client.go @@ -28,6 +28,11 @@ var reservedIPRanges []*net.IPNet // IsReservedIP checks whether the target IP belongs to reserved IP address ranges to avoid SSRF attacks to the internal // network of the Mattermost server func IsReservedIP(ip net.IP) bool { + // Canonicalize IPv4-mapped IPv6 addresses (e.g., ::ffff:127.0.0.1) to their + // native IPv4 form so that IPv4 CIDR ranges match correctly. + if ip4 := ip.To4(); ip4 != nil { + ip = ip4 + } for _, ipRange := range reservedIPRanges { if ipRange.Contains(ip) { return true diff --git a/server/public/shared/httpservice/client_test.go b/server/public/shared/httpservice/client_test.go index 49fc1485226..40de6b26119 100644 --- a/server/public/shared/httpservice/client_test.go +++ b/server/public/shared/httpservice/client_test.go @@ -211,6 +211,17 @@ func TestIsReservedIP(t *testing.T) { {"127.120.6.3", net.IPv4(127, 120, 6, 3), true}, {"8.8.8.8", net.IPv4(8, 8, 8, 8), false}, {"9.9.9.9", net.IPv4(9, 9, 9, 8), false}, + // IPv4-mapped IPv6 addresses should be detected as reserved + {"::ffff:127.0.0.1", net.ParseIP("::ffff:127.0.0.1"), true}, + {"::ffff:192.168.1.1", net.ParseIP("::ffff:192.168.1.1"), true}, + {"::ffff:10.0.0.1", net.ParseIP("::ffff:10.0.0.1"), true}, + {"::ffff:169.254.169.254", net.ParseIP("::ffff:169.254.169.254"), true}, + {"::ffff:8.8.8.8", net.ParseIP("::ffff:8.8.8.8"), false}, + // Pure IPv6 reserved addresses + {"::1", net.ParseIP("::1"), true}, + {"fe80::1", net.ParseIP("fe80::1"), true}, + // Public IPv6 + {"2607:f8b0:4004:800::200e", net.ParseIP("2607:f8b0:4004:800::200e"), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {