From e86257e866f8b0d31a19e75ab0e1ba36147df854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Sun, 15 Mar 2026 04:11:42 +0000 Subject: [PATCH] =?UTF-8?q?BackUrl::sanitizeForLinks=20=E5=8A=A0=E5=BC=BA?= =?UTF-8?q?=EF=BC=9A=E6=8B=92=E7=BB=9D=E4=BA=8C=E6=AC=A1=E7=BC=96=E7=A0=81?= =?UTF-8?q?=20back%3D=20=E7=BB=95=E8=BF=87=20=E5=B9=B6=E8=A1=A5=E5=8D=95?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Support/BackUrl.php | 8 ++++++++ tests/Unit/BackUrlSanitizeForLinksTest.php | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/app/Support/BackUrl.php b/app/Support/BackUrl.php index 706a58e..5e92a3e 100644 --- a/app/Support/BackUrl.php +++ b/app/Support/BackUrl.php @@ -27,9 +27,17 @@ class BackUrl } // 拒绝 back 自身再包含 back=(避免无限嵌套导致 URL 膨胀,且容易绕过页面侧护栏) + // 同时拒绝“二次编码”的 back%3D(例如 %2526back%253D 经过一次 urldecode 后变成 %26back%3D, + // 在浏览器点击后会再次被解码为 &back=,形成绕过)。 if (preg_match('/(?:^|[?&])back=/', $incomingBack)) { return ''; } + if (preg_match('/(?:^|[?&]|%26|%3f)back%3d/i', $incomingBack)) { + return ''; + } + if (preg_match('/back%253d/i', $incomingBack)) { + return ''; + } return $incomingBack; } diff --git a/tests/Unit/BackUrlSanitizeForLinksTest.php b/tests/Unit/BackUrlSanitizeForLinksTest.php index 92aa875..df6c182 100644 --- a/tests/Unit/BackUrlSanitizeForLinksTest.php +++ b/tests/Unit/BackUrlSanitizeForLinksTest.php @@ -31,6 +31,10 @@ class BackUrlSanitizeForLinksTest extends TestCase $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