fix(security): URL import with own range check and a generic error (audit M3, cheap part)
PublicAddress blocks every private/reserved IPv4 and IPv6 range (169.254/16, 100.64/10, fc00::/7, fe80::/10, IPv4-mapped/NAT64/6to4 by their embedded address, ...) independent of the core version; checked before the request and on every redirect (Requests before_redirect). Network failures return only "Die Datei konnte nicht geladen werden."; the detail goes to the error log. DNS-rebinding pinning stays deferred. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Media;
|
||||
|
||||
use LIT\MCP\Integrations\Media\PublicAddress;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Security audit M3: the plugin's own private/reserved range check for URL imports, independent of
|
||||
* the WordPress version's wp_http_validate_url() list.
|
||||
*/
|
||||
final class PublicAddressTest extends TestCase
|
||||
{
|
||||
/** @return array<string, array{string}> */
|
||||
public static function blocked(): array
|
||||
{
|
||||
$ips = [
|
||||
'0.0.0.0', '10.1.2.3', '100.64.0.1', '100.127.255.254', '127.0.0.1', '169.254.169.254',
|
||||
'172.16.0.1', '172.31.255.255', '192.0.0.8', '192.0.2.1', '192.168.1.1', '198.18.0.1',
|
||||
'198.51.100.7', '203.0.113.9', '224.0.0.1', '240.0.0.1', '255.255.255.255',
|
||||
'::', '::1', 'fc00::1', 'fd12:3456::1', 'fe80::1', 'febf::1', 'ff02::1', '2001:db8::1',
|
||||
'::ffff:127.0.0.1', '::ffff:10.0.0.1', '::ffff:169.254.169.254', '::ffff:a9fe:a9fe',
|
||||
'64:ff9b::a9fe:a9fe', '::127.0.0.1',
|
||||
];
|
||||
|
||||
return array_combine($ips, array_map(static fn (string $ip): array => [$ip], $ips));
|
||||
}
|
||||
|
||||
/** @dataProvider blocked */
|
||||
public function testPrivateAndReservedAddressesAreNotPublic(string $ip): void
|
||||
{
|
||||
self::assertFalse(PublicAddress::isPublicIp($ip));
|
||||
}
|
||||
|
||||
public function testPublicAddressesPass(): void
|
||||
{
|
||||
foreach (['93.184.216.34', '100.63.255.255', '100.128.0.1', '172.32.0.1', '8.8.8.8', '2606:4700::1111', '::ffff:8.8.8.8'] as $ip) {
|
||||
self::assertTrue(PublicAddress::isPublicIp($ip), $ip);
|
||||
}
|
||||
|
||||
self::assertFalse(PublicAddress::isPublicIp('not-an-ip'));
|
||||
}
|
||||
|
||||
public function testHostsAreResolvedAndEveryAddressMustBePublic(): void
|
||||
{
|
||||
$resolve = static fn (string $host): array => [
|
||||
'cdn.example.com' => ['93.184.216.34'],
|
||||
'mixed.example.com' => ['93.184.216.34', '169.254.169.254'],
|
||||
'metadata.example.com' => ['169.254.169.254'],
|
||||
][$host] ?? [];
|
||||
|
||||
self::assertTrue(PublicAddress::hostIsPublic('cdn.example.com', $resolve));
|
||||
self::assertFalse(PublicAddress::hostIsPublic('mixed.example.com', $resolve));
|
||||
self::assertFalse(PublicAddress::hostIsPublic('metadata.example.com', $resolve));
|
||||
self::assertFalse(PublicAddress::hostIsPublic('unresolvable.example.com', $resolve));
|
||||
self::assertFalse(PublicAddress::hostIsPublic('169.254.169.254', $resolve), 'IP literal, no lookup');
|
||||
self::assertFalse(PublicAddress::hostIsPublic('[fe80::1]', $resolve), 'IPv6 literal in brackets');
|
||||
self::assertTrue(PublicAddress::hostIsPublic('8.8.8.8', $resolve));
|
||||
self::assertFalse(PublicAddress::hostIsPublic('', $resolve));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user