fix(back): index 页 back 校验拒绝 nested back + 调整测试断言

This commit is contained in:
萝卜
2026-03-14 02:12:29 +00:00
parent 7479eb0e77
commit bb40ff692c
4 changed files with 27 additions and 7 deletions

View File

@@ -34,7 +34,10 @@
$incomingBack = (string) request()->query('back', ''); $incomingBack = (string) request()->query('back', '');
// 为避免 & 被 Blade escape 成 & 导致回退上下文丢失,这里需要原样输出 href。 // 为避免 & 被 Blade escape 成 & 导致回退上下文丢失,这里需要原样输出 href。
// 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 XSS 风险。 // 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 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 ? $incomingBack
: ''; : '';
@endphp @endphp
@@ -78,7 +81,10 @@
// “全部”:清空筛选,但保留 back用于返回来源页 // “全部”:清空筛选,但保留 back用于返回来源页
$incomingBack = (string) request()->query('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 ? $incomingBack
: ''; : '';
$allUrl = '/admin/plans'; $allUrl = '/admin/plans';

View File

@@ -25,7 +25,10 @@
$back = (string) request()->query('back', ''); $back = (string) request()->query('back', '');
// 为避免 & 被 Blade escape 成 &amp; 导致回退上下文丢失,这里需要原样输出 href。 // 为避免 & 被 Blade escape 成 &amp; 导致回退上下文丢失,这里需要原样输出 href。
// 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 XSS 风险。 // 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 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 ? $back
: ''; : '';
@endphp @endphp
@@ -113,7 +116,10 @@
// “全部”:清空筛选,但保留 back用于返回来源页 // “全部”:清空筛选,但保留 back用于返回来源页
$incomingBack = (string) request()->query('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 ? $incomingBack
: ''; : '';
$allUrl = '/admin/platform-orders'; $allUrl = '/admin/platform-orders';

View File

@@ -42,7 +42,10 @@
$incomingBack = (string) request()->query('back', ''); $incomingBack = (string) request()->query('back', '');
// 为避免 & 被 Blade escape 成 &amp; 导致回退上下文丢失,这里需要原样输出 href。 // 为避免 & 被 Blade escape 成 &amp; 导致回退上下文丢失,这里需要原样输出 href。
// 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 XSS 风险。 // 安全护栏:必须为站内相对路径,并拒绝引号/尖括号,降低 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 ? $incomingBack
: ''; : '';
@endphp @endphp
@@ -88,7 +91,10 @@
// “全部”:清空筛选,但保留 back用于返回来源页 // “全部”:清空筛选,但保留 back用于返回来源页
$incomingBack = (string) request()->query('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 ? $incomingBack
: ''; : '';
$allUrl = '/admin/site-subscriptions'; $allUrl = '/admin/site-subscriptions';

View File

@@ -38,6 +38,8 @@ class AdminPlatformOrderIndexBackLinkNotEscapedTest extends TestCase
$this->get('/admin/platform-orders?back=' . urlencode('https://evil.example.com/?x=1&y=2')) $this->get('/admin/platform-orders?back=' . urlencode('https://evil.example.com/?x=1&y=2'))
->assertOk() ->assertOk()
->assertDontSee('返回上一页(保留上下文)'); // 页面仍会出现“返回上一页保留上下文”文案其它位置也有例如治理SOP卡提示
// 因此这里改为断言:不应出现该 external back 的 href。
->assertDontSee('href="https://evil.example.com/?x=1&y=2"', false);
} }
} }