assertSame('/admin/platform-orders', BackUrl::sanitizeForLinks('/admin/platform-orders')); } public function test_sanitize_for_links_should_reject_too_long_back(): void { $long = '/admin/x?' . str_repeat('a', 5000); $this->assertSame('', BackUrl::sanitizeForLinks($long)); } public function test_sanitize_for_links_should_reject_absolute_urls(): void { $this->assertSame('', BackUrl::sanitizeForLinks('https://evil.com/a')); $this->assertSame('', BackUrl::sanitizeForLinks('http://evil.com/a')); // 协议相对 URL $this->assertSame('', BackUrl::sanitizeForLinks('//evil.com/a')); } public function test_sanitize_for_links_should_reject_quotes_and_angle_brackets(): void { $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?keyword="a"')); $this->assertSame('', BackUrl::sanitizeForLinks("/admin/x?keyword='a'")); $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?a=')); $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?a=>')); // 控制字符/CRLF 注入 $this->assertSame('', BackUrl::sanitizeForLinks("/admin/x?x=1\nSet-Cookie:evil=1")); $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?x=1%0aSet-Cookie:evil=1')); } public function test_sanitize_for_links_should_reject_nested_back_param(): void { $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?back=/admin/y')); $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?a=1&back=/admin/y')); $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?a=1&b=2&back=/admin/y')); // 二次编码绕过:%26back%3D 在浏览器/中间层解码后会变回 &back=,因此也应拒绝 $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?a=1%26back%3D/admin/y')); $this->assertSame('', BackUrl::sanitizeForLinks('/admin/x?a=1%2526back%253D/admin/y')); } public function test_sanitize_for_links_should_reject_paths_not_starting_with_slash(): void { $this->assertSame('', BackUrl::sanitizeForLinks('admin/platform-orders')); $this->assertSame('', BackUrl::sanitizeForLinks('../admin/platform-orders')); $this->assertSame('', BackUrl::sanitizeForLinks('javascript:alert(1)')); } }