Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Imaging/RemoteUrlValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ protected function ensureHostResolvesToPublicIps($host)

protected function assertPublicIp($ip)
{
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$packed = inet_pton($ip);
if ($packed !== false && substr($packed, 0, 12) === "\0\0\0\0\0\0\0\0\0\0\xff\xff") {
$ip = inet_ntop(substr($packed, 12));
}
}

$result = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);

if (! $result) {
Expand Down
32 changes: 32 additions & 0 deletions tests/Imaging/ImageGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,38 @@ public function it_blocks_watermark_urls_that_target_non_public_ip_ranges()
$this->makeGenerator()->setParams(['mark' => 'http://127.0.0.1/watermark.png']);
}

public static function ipv4MappedIpv6Provider()
{
return [
'mapped loopback' => ['::ffff:127.0.0.1'],
'mapped loopback (hex form)' => ['::ffff:7f00:1'],
'mapped RFC1918' => ['::ffff:10.0.0.1'],
'mapped link-local metadata' => ['::ffff:169.254.169.254'],
];
}

#[Test]
#[DataProvider('ipv4MappedIpv6Provider')]
public function it_blocks_ipv4_mapped_ipv6_addresses_via_dns($mappedIp)
{
$validator = new RemoteUrlValidator(fn ($host) => [['ipv6' => $mappedIp]]);

$this->expectException(InvalidRemoteUrlException::class);
$this->expectExceptionMessage('Destination IP is not publicly routable.');

$validator->validate('https://attacker.example/foo.jpg');
}

#[Test]
public function it_allows_public_ipv6_addresses()
{
$validator = new RemoteUrlValidator(fn ($host) => [['ipv6' => '2606:4700:4700::1111']]);

$validator->validate('https://example.com/foo.jpg');

$this->addToAssertionCount(1);
}

#[Test]
public function the_watermark_disk_is_the_public_directory_by_default()
{
Expand Down
Loading