Korjaa IPv6 IP-allow: IPv4-mapped IPv6 ↔ IPv4 cross-matching
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 <noreply@anthropic.com>
This commit is contained in:
41
api.php
41
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;
|
||||
|
||||
Reference in New Issue
Block a user