From 6628eaeb89440dd2cc04bbc6dd09b1fb32e26d62 Mon Sep 17 00:00:00 2001 From: Jukka Lampikoski Date: Wed, 11 Mar 2026 18:02:16 +0200 Subject: [PATCH] =?UTF-8?q?Korjaa=20IPv6=20IP-allow:=20IPv4-mapped=20IPv6?= =?UTF-8?q?=20=E2=86=94=20IPv4=20cross-matching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kun palvelin käyttää IPv6:ta, client IP voi tulla muodossa ::ffff:192.168.1.1 vaikka allow-listassa on 192.168.1.1. Nyt isIpAllowed() tunnistaa IPv4-mapped IPv6 -osoitteet ja vertailee molemmissa muodoissa (IPv4 ↔ ::ffff:IPv4). Co-Authored-By: Claude Opus 4.6 --- api.php | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/api.php b/api.php index d3339c2..d1bcc00 100644 --- a/api.php +++ b/api.php @@ -146,8 +146,22 @@ function isIpAllowed(string $ip, string $allowedIps): bool { $allowedIps = trim($allowedIps); if ($allowedIps === '' || strtolower($allowedIps) === 'kaikki') return true; $entries = preg_split('/[\s,]+/', $allowedIps, -1, PREG_SPLIT_NO_EMPTY); + + // Normalisoi IP: IPv4-mapped IPv6 (::ffff:1.2.3.4) → myös IPv4 muotoon $ipBin = @inet_pton($ip); if ($ipBin === false) return false; + + // Jos IP on IPv4-mapped IPv6 (::ffff:x.x.x.x), kokeile myös puhtaana IPv4:nä + $ipv4Equivalent = null; + if (strlen($ipBin) === 16 && substr($ipBin, 0, 12) === "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff") { + $ipv4Equivalent = inet_ntop(substr($ipBin, 12)); + } + // Jos IP on puhdas IPv4, kokeile myös mapped-muodossa + $ipv6MappedBin = null; + if (strlen($ipBin) === 4) { + $ipv6MappedBin = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" . $ipBin; + } + foreach ($entries as $entry) { $entry = trim($entry); if ($entry === '') continue; @@ -157,19 +171,34 @@ function isIpAllowed(string $ip, string $allowedIps): bool { $bits = (int)$bits; $subnetBin = @inet_pton($subnet); if ($subnetBin === false) continue; - // IPv4 = 4 tavua, IPv6 = 16 tavua — pitää olla sama perhe - if (strlen($ipBin) !== strlen($subnetBin)) continue; - $maxBits = strlen($ipBin) * 8; + $maxBits = strlen($subnetBin) * 8; if ($bits < 0 || $bits > $maxBits) continue; // Rakenna bittimask $mask = str_repeat("\xff", intdiv($bits, 8)); if ($bits % 8) $mask .= chr(0xff << (8 - ($bits % 8))); - $mask = str_pad($mask, strlen($ipBin), "\x00"); - if (($ipBin & $mask) === ($subnetBin & $mask)) return true; + $mask = str_pad($mask, strlen($subnetBin), "\x00"); + + // Tarkista suoraan + if (strlen($ipBin) === strlen($subnetBin) && ($ipBin & $mask) === ($subnetBin & $mask)) return true; + // Tarkista IPv4-equivalent + if ($ipv4Equivalent) { + $ipv4Bin = @inet_pton($ipv4Equivalent); + if ($ipv4Bin && strlen($ipv4Bin) === strlen($subnetBin) && ($ipv4Bin & $mask) === ($subnetBin & $mask)) return true; + } + // Tarkista IPv6-mapped + if ($ipv6MappedBin && strlen($ipv6MappedBin) === strlen($subnetBin) && ($ipv6MappedBin & $mask) === ($subnetBin & $mask)) return true; } else { // Yksittäinen IP (IPv4 tai IPv6) $entryBin = @inet_pton($entry); - if ($entryBin !== false && $ipBin === $entryBin) return true; + if ($entryBin === false) continue; + if ($ipBin === $entryBin) return true; + // Tarkista IPv4-equivalent + if ($ipv4Equivalent) { + $ipv4Bin = @inet_pton($ipv4Equivalent); + if ($ipv4Bin && $ipv4Bin === $entryBin) return true; + } + // Tarkista IPv6-mapped + if ($ipv6MappedBin && $ipv6MappedBin === $entryBin) return true; } } return false;