diff --git a/lib/private/Security/Normalizer/IpAddress.php b/lib/private/Security/Normalizer/IpAddress.php index 705235413e499..98d85ce07a145 100644 --- a/lib/private/Security/Normalizer/IpAddress.php +++ b/lib/private/Security/Normalizer/IpAddress.php @@ -92,6 +92,38 @@ private function getIPv6Subnet(string $ip, int $maskBits = 48): string { return \inet_ntop($binary).'/'.$maskBits; } + /** + * Returns the IPv4 address embedded in an IPv6 if applicable. + * The detected format is "::ffff:x.x.x.x" using the binary form. + * + * @return string|null embedded IPv4 string or null if none was found + */ + private function getEmbeddedIpv4(string $ipv6): ?string { + $binary = inet_pton($ipv6); + if (!$binary) { + return null; + } + for ($i = 0; $i <= 9; $i++) { + if (unpack('C', $binary[$i])[1] !== 0) { + return null; + } + } + + for ($i = 10; $i <= 11; $i++) { + if (unpack('C', $binary[$i])[1] !== 255) { + return null; + } + } + + $binary4 = ''; + for ($i = 12; $i < 16; $i++) { + $binary4 .= $binary[$i]; + } + + return inet_ntop($binary4); + } + + /** * Gets either the /32 (IPv4) or the /64 (IPv6) subnet of an IP address * @@ -104,6 +136,15 @@ public function getSubnet(): string { 32 ); } + + $ipv4 = $this->getEmbeddedIpv4($this->ip); + if ($ipv4 !== null) { + return $this->getIPv4Subnet( + $ipv4, + 32 + ); + } + return $this->getIPv6Subnet( $this->ip, 64 diff --git a/tests/lib/Security/Normalizer/IpAddressTest.php b/tests/lib/Security/Normalizer/IpAddressTest.php index b202ecd7234d5..bb487659ced39 100644 --- a/tests/lib/Security/Normalizer/IpAddressTest.php +++ b/tests/lib/Security/Normalizer/IpAddressTest.php @@ -38,6 +38,18 @@ public function subnetDataProvider() { '192.168.0.123', '192.168.0.123/32', ], + [ + '::ffff:192.168.0.123', + '192.168.0.123/32', + ], + [ + '0:0:0:0:0:ffff:192.168.0.123', + '192.168.0.123/32', + ], + [ + '0:0:0:0:0:ffff:c0a8:7b', + '192.168.0.123/32', + ], [ '2001:0db8:85a3:0000:0000:8a2e:0370:7334', '2001:db8:85a3::/64',