diff --git a/resources/views/admin/plans/index.blade.php b/resources/views/admin/plans/index.blade.php index fd46f79..2d04747 100644 --- a/resources/views/admin/plans/index.blade.php +++ b/resources/views/admin/plans/index.blade.php @@ -34,7 +34,10 @@ $incomingBack = (string) request()->query('back', ''); // 为避免 & 被 Blade escape 成 & 导致回退上下文丢失,这里需要原样输出 href。 // 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 XSS 风险。 - $safeBack = (str_starts_with($incomingBack, '/') && !preg_match('/["\'<>]/', $incomingBack)) + $safeBack = (str_starts_with($incomingBack, '/') + && !preg_match('/["\'<>]/', $incomingBack) + // back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) + && !preg_match('/(?:^|[?&])back=/', $incomingBack)) ? $incomingBack : ''; @endphp @@ -78,7 +81,10 @@ // “全部”:清空筛选,但保留 back(用于返回来源页) $incomingBack = (string) request()->query('back', ''); - $safeBack2 = (str_starts_with($incomingBack, '/') && !preg_match('/["\'<>]/', $incomingBack)) + $safeBack2 = (str_starts_with($incomingBack, '/') + && !preg_match('/["\'<>]/', $incomingBack) + // back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) + && !preg_match('/(?:^|[?&])back=/', $incomingBack)) ? $incomingBack : ''; $allUrl = '/admin/plans'; diff --git a/resources/views/admin/platform_orders/index.blade.php b/resources/views/admin/platform_orders/index.blade.php index 2ded344..6d5e452 100644 --- a/resources/views/admin/platform_orders/index.blade.php +++ b/resources/views/admin/platform_orders/index.blade.php @@ -25,7 +25,10 @@ $back = (string) request()->query('back', ''); // 为避免 & 被 Blade escape 成 & 导致回退上下文丢失,这里需要原样输出 href。 // 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 XSS 风险。 - $safeBack = (str_starts_with($back, '/') && !preg_match('/["\'<>]/', $back)) + $safeBack = (str_starts_with($back, '/') + && !preg_match('/["\'<>]/', $back) + // back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) + && !preg_match('/(?:^|[?&])back=/', $back)) ? $back : ''; @endphp @@ -113,7 +116,10 @@ // “全部”:清空筛选,但保留 back(用于返回来源页) $incomingBack = (string) request()->query('back', ''); - $safeBack = (str_starts_with($incomingBack, '/') && !preg_match('/["\'<>]/', $incomingBack)) + $safeBack = (str_starts_with($incomingBack, '/') + && !preg_match('/["\'<>]/', $incomingBack) + // back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) + && !preg_match('/(?:^|[?&])back=/', $incomingBack)) ? $incomingBack : ''; $allUrl = '/admin/platform-orders'; diff --git a/resources/views/admin/site_subscriptions/index.blade.php b/resources/views/admin/site_subscriptions/index.blade.php index fa412eb..d3828ca 100644 --- a/resources/views/admin/site_subscriptions/index.blade.php +++ b/resources/views/admin/site_subscriptions/index.blade.php @@ -42,7 +42,10 @@ $incomingBack = (string) request()->query('back', ''); // 为避免 & 被 Blade escape 成 & 导致回退上下文丢失,这里需要原样输出 href。 // 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 XSS 风险。 - $safeBack = (str_starts_with($incomingBack, '/') && !preg_match('/["\'<>]/', $incomingBack)) + $safeBack = (str_starts_with($incomingBack, '/') + && !preg_match('/["\'<>]/', $incomingBack) + // back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) + && !preg_match('/(?:^|[?&])back=/', $incomingBack)) ? $incomingBack : ''; @endphp @@ -88,7 +91,10 @@ // “全部”:清空筛选,但保留 back(用于返回来源页) $incomingBack = (string) request()->query('back', ''); - $safeBack = (str_starts_with($incomingBack, '/') && !preg_match('/["\'<>]/', $incomingBack)) + $safeBack = (str_starts_with($incomingBack, '/') + && !preg_match('/["\'<>]/', $incomingBack) + // back 本身不应再包含 back(避免无限嵌套导致 URL 膨胀) + && !preg_match('/(?:^|[?&])back=/', $incomingBack)) ? $incomingBack : ''; $allUrl = '/admin/site-subscriptions'; diff --git a/tests/Feature/AdminPlatformOrderIndexBackLinkNotEscapedTest.php b/tests/Feature/AdminPlatformOrderIndexBackLinkNotEscapedTest.php index 566dee6..566bd6f 100644 --- a/tests/Feature/AdminPlatformOrderIndexBackLinkNotEscapedTest.php +++ b/tests/Feature/AdminPlatformOrderIndexBackLinkNotEscapedTest.php @@ -38,6 +38,8 @@ class AdminPlatformOrderIndexBackLinkNotEscapedTest extends TestCase $this->get('/admin/platform-orders?back=' . urlencode('https://evil.example.com/?x=1&y=2')) ->assertOk() - ->assertDontSee('返回上一页(保留上下文)'); + // 页面仍会出现“返回上一页(保留上下文)”文案(其它位置也有,例如治理SOP卡提示), + // 因此这里改为断言:不应出现该 external back 的 href。 + ->assertDontSee('href="https://evil.example.com/?x=1&y=2"', false); } }