diff --git a/app/[locale]/docs/[...slug]/page.tsx b/app/[locale]/docs/[...slug]/page.tsx index 2a13a16..7ca6b3d 100644 --- a/app/[locale]/docs/[...slug]/page.tsx +++ b/app/[locale]/docs/[...slug]/page.tsx @@ -1,4 +1,5 @@ import { source } from "@/lib/source"; +import { safeJsonLdString } from "@/lib/json-ld"; import { SITE_URL } from "@/lib/site-url"; import { DocsPage, DocsBody } from "fumadocs-ui/page"; import { notFound } from "next/navigation"; @@ -104,12 +105,12 @@ export default async function DocPage({ params }: Param) { "})` + 输出不能包含字面 `<` 或 ``,并且应包含转义后的 `\\u003c` 序列。 +- **为什么**:`JSON.stringify` 默认不转义 `<` `>` `&`,攻击者把 + `` + 写进任何 user-generated 字段(profile bio、displayName 等)即触发 stored XSS。 + satoken 存在 localStorage 且写入非 HttpOnly cookie(跨子域 pgAdmin 的设计取舍), + 一次 XSS 等于完整账户接管。 +- **历史**:2026-05-07 三方 CR attack chain A 起点(详见内部报告)。 diff --git a/lib/json-ld.ts b/lib/json-ld.ts new file mode 100644 index 0000000..4fdd7bf --- /dev/null +++ b/lib/json-ld.ts @@ -0,0 +1,34 @@ +/** + * 把任意对象序列化为可安全嵌入 + * JSON.stringify 默认不转义 `<`,攻击者文本作为合法 JSON 字符串嵌入 闭合 script 块,接着把后续 ` + * + * JSON.stringify 默认输出原文,浏览器看到 `` 就闭合 script block, + * 接着把后续 ` 不再出现在输出里", () => { + const payload = { + bio: ``, + }; + const out = safeJsonLdString(payload); + expect(out).not.toContain(""); + expect(out).not.toContain("载荷 with & 'quotes'` }; + const out = safeJsonLdString(original); + const parsed = JSON.parse(out); + expect(parsed.bio).toBe(original.bio); + }); +});