mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 19:22:48 +00:00
102 lines
3.5 KiB
PHP
102 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Rules;
|
|
|
|
use App\Rules\ExternalUrl;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use Tests\TestCase;
|
|
|
|
class ExternalUrlTest extends TestCase
|
|
{
|
|
private function passes(string $url): bool
|
|
{
|
|
return Validator::make(
|
|
['url' => $url],
|
|
['url' => [new ExternalUrl]],
|
|
)->passes();
|
|
}
|
|
|
|
public static function rejectedProvider(): array
|
|
{
|
|
return [
|
|
// Non-http schemes are gone even if the URL is otherwise well-formed.
|
|
'ftp scheme' => ['ftp://example.com/'],
|
|
'irc scheme' => ['irc://example.com/'],
|
|
'javascript scheme' => ['javascript:alert(1)'],
|
|
'file scheme' => ['file:///etc/passwd'],
|
|
'gopher scheme' => ['gopher://example.com/'],
|
|
|
|
// IMDS / cloud metadata.
|
|
'aws metadata' => ['http://169.254.169.254/latest/meta-data/'],
|
|
'aws metadata https' => ['https://169.254.169.254/'],
|
|
|
|
// Loopback.
|
|
'ipv4 loopback' => ['http://127.0.0.1/'],
|
|
'ipv4 loopback alt' => ['http://127.0.0.53/'],
|
|
'ipv6 loopback' => ['http://[::1]/'],
|
|
'localhost host' => ['http://localhost/'],
|
|
|
|
// RFC-1918.
|
|
'rfc1918 10' => ['http://10.0.0.1/'],
|
|
'rfc1918 172' => ['http://172.16.5.5/'],
|
|
'rfc1918 192' => ['http://192.168.1.1/'],
|
|
|
|
// Link-local / unspecified / broadcast.
|
|
'link local v4' => ['http://169.254.1.1/'],
|
|
'link local v6' => ['http://[fe80::1]/'],
|
|
'unspecified v4' => ['http://0.0.0.0/'],
|
|
|
|
// IPv6 unique-local.
|
|
'unique local v6' => ['http://[fc00::1]/'],
|
|
'unique local v6 fd' => ['http://[fd12:3456::1]/'],
|
|
|
|
// IPv4-mapped IPv6 sneak-in.
|
|
'v4-mapped loopback' => ['http://[::ffff:127.0.0.1]/'],
|
|
'v4-mapped rfc1918' => ['http://[::ffff:10.0.0.1]/'],
|
|
|
|
// Malformed.
|
|
'no scheme' => ['example.com'],
|
|
'no host' => ['http:///'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('rejectedProvider')]
|
|
public function test_rejects_dangerous_or_malformed_urls(string $url)
|
|
{
|
|
$this->assertFalse($this->passes($url), 'Should have been rejected: '.$url);
|
|
}
|
|
|
|
public function test_accepts_public_ipv4_literal()
|
|
{
|
|
// Public IP literal — no DNS lookup required.
|
|
$this->assertTrue($this->passes('http://93.184.216.34/'));
|
|
}
|
|
|
|
public function test_accepts_public_ipv6_literal()
|
|
{
|
|
$this->assertTrue($this->passes('http://[2606:2800:220:1:248:1893:25c8:1946]/'));
|
|
}
|
|
|
|
public function test_internal_targets_pass_when_escape_hatch_is_enabled()
|
|
{
|
|
config(['app.webhook_allow_internal_targets' => true]);
|
|
|
|
$this->assertTrue($this->passes('http://127.0.0.1/hook'));
|
|
$this->assertTrue($this->passes('http://10.0.0.1/hook'));
|
|
$this->assertTrue($this->passes('http://192.168.1.50:8080/hook'));
|
|
$this->assertTrue($this->passes('http://169.254.169.254/'));
|
|
$this->assertTrue($this->passes('http://[::1]/hook'));
|
|
}
|
|
|
|
public function test_escape_hatch_still_blocks_non_http_schemes()
|
|
{
|
|
config(['app.webhook_allow_internal_targets' => true]);
|
|
|
|
$this->assertFalse($this->passes('ftp://example.com/'));
|
|
$this->assertFalse($this->passes('irc://example.com/'));
|
|
$this->assertFalse($this->passes('javascript:alert(1)'));
|
|
$this->assertFalse($this->passes('file:///etc/passwd'));
|
|
}
|
|
}
|