Files
XC_VM/tests/Unit/NetworkUtilsTest.php
T
obscuremindandClaude Opus 5.5 6e2da9aad0 fix(security): ip_subnet_match compares subnets, IPv6 included
The comparison dropped what followed the last dot of each address. An
IPv6 address has no dots, so nothing was left of either and any two of
them matched: with ip_subnet_match on, a token or a session held from
one IPv6 network passed as another's (stream tokens, admin and reseller
sessions; where IPv6 addresses arrive, behind a CDN for one).

NetworkUtils::ipMatches() now compares the /24 of an IPv4 address and
the /64 of an IPv6 one, takes an IPv4-mapped address as its IPv4, and
matches one of each or what is not an address only when equal. The
eight inline copies of the old comparison call it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AneTzAHPPkYWPQjwyZ2td9
2026-10-10 14:07:24 +00:00

47 lines
2.0 KiB
PHP

<?php
use XcVm\Core\Util\NetworkUtils;
use PHPUnit\Framework\TestCase;
/**
* @covers XcVm\Core\Util\NetworkUtils
*/
final class NetworkUtilsTest extends TestCase {
public function testExactMatchWhenSubnetOff() {
$this->assertTrue(NetworkUtils::ipMatches(false, '1.2.3.4', '1.2.3.4'));
$this->assertFalse(NetworkUtils::ipMatches(false, '1.2.3.4', '1.2.3.5'));
}
public function testSubnetMatchIgnoresLastOctet() {
$this->assertTrue(NetworkUtils::ipMatches(true, '1.2.3.4', '1.2.3.99'));
$this->assertFalse(NetworkUtils::ipMatches(true, '1.2.3.4', '1.2.9.4'));
}
/**
* IPv6 has no dots: the comparison dropped "what follows the last dot" of
* each address, which left nothing of either, and any two IPv6 addresses
* matched. Their subnet is the /64; a mapped IPv4 address is its IPv4.
*/
public function testSubnetMatchOfIpv6IsItsSlash64() {
$this->assertFalse(NetworkUtils::ipMatches(true, '2001:db8:1:2::1', '2a02:1234:5:6::9'), 'two unrelated networks');
$this->assertFalse(NetworkUtils::ipMatches(true, '2001:db8:1:2::1', '2001:db8:1:3::1'), 'another /64');
$this->assertTrue(NetworkUtils::ipMatches(true, '2001:db8:1:2::1', '2001:db8:1:2:aaaa:bbbb:cccc:dddd'), 'the same /64');
$this->assertTrue(NetworkUtils::ipMatches(true, '::ffff:1.2.3.4', '1.2.3.99'), 'a mapped IPv4 address is that address');
$this->assertFalse(NetworkUtils::ipMatches(true, '::ffff:1.2.3.4', '::ffff:9.9.9.9'));
$this->assertFalse(NetworkUtils::ipMatches(true, '1.2.3.4', '2001:db8:1:2::1'), 'one of each');
}
public function testWhatIsNotAnAddressMatchesOnlyItself() {
$this->assertFalse(NetworkUtils::ipMatches(true, '', '2001:db8::1'));
$this->assertFalse(NetworkUtils::ipMatches(true, 'abc', 'xyz'));
$this->assertFalse(NetworkUtils::ipMatches(true, null, '1.2.3.4'));
$this->assertTrue(NetworkUtils::ipMatches(true, 'unknown', 'unknown'));
}
public function testNullIpsAreHandled() {
$this->assertTrue(NetworkUtils::ipMatches(false, null, null));
$this->assertFalse(NetworkUtils::ipMatches(false, '1.2.3.4', null));
}
}