Discourse AI XSS 漏洞(CVE-2026-27740):当 LLM 输出被当作可信 HTML 渲染
一条被举报的帖子、一个 AI 审核员、一次 htmlSafe 调用。Discourse AI 插件将 LLM 输出视为可信标记,使间接提示注入演变为针对管理员的 XSS。2026 年 3 月 19 日公开。
这是什么?
2026 年 3 月 19 日,Discourse 团队发布了 GHSA-95hc-42c6-wvvr 安全公告并获得编号 CVE-2026-27740 —— 这是一个位于 Discourse AI 插件中的存储型跨站脚本(XSS)漏洞,触发方式是对一个 LLM 审核员实施间接提示注入。该漏洞当天即录入 NVD,CVSS 4.0 评分为 5.1(MEDIUM),已在 Discourse 2026.3.0-latest.1、2026.2.1 和 2026.1.2 中修复。
漏洞的形态简短而具有教学意义:一个 AI 分诊自动化任务读取被举报的帖子,请 LLM 总结举报理由,然后将该摘要在管理员的审核队列(Review Queue)中呈现。渲染器对 LLM 的响应调用了 htmlSafe。攻击者撰写一条专门用于操纵模型的帖子,诱使模型在”理由”字段中返回一个 <script> 标签,管理员下次打开队列时该 payload 即被执行。这就是 OWASP LLM05 —— 输出处理不当(Improper Output Handling)—— 在真实环境中的体现(OWASP LLM Top 10)。
工作原理
参与者有三方:撰写帖子的攻击者、调用 LLM 的 AI 分诊任务,以及打开审核队列的管理员。
[攻击者帖子]
│ 内容专门用于操纵 LLM 的"理由"字段
▼
[Discourse AI 分诊自动化]
│ llm_triage.rb —— 将帖子发送给配置的 LLM
▼
[LLM 响应]
│ 包含攻击者控制的标记,例如类似 script 的 payload
▼
[I18n 模板 "discourse_automation.scriptables.llm_triage.flagged_post"]
│ 以 `htmlSafe` 方式插入 llm_response 和 automation_name
▼
[管理员浏览器中的 Review Queue UI]
│ payload 在已认证的管理员会话中执行
▼
[会话令牌窃取 / 管理操作 / 配置篡改]
根本原因记录在修复补丁 44b84439 中。修复前,plugins/discourse-ai/lib/automation/llm_triage.rb 将 LLM 的响应直接传入翻译调用:
I18n.t(
"discourse_automation.scriptables.llm_triage.flagged_post",
base_path: Discourse.base_path,
llm_response: result, # 原始 LLM 输出
automation_name: automation&.name.to_s
)
flagged_post 模板使用 htmlSafe 渲染这些值,而 htmlSafe 正是 Rails 中显式跳过 HTML 转义的方式。补丁用 ERB::Util.html_escape 包裹两个字段:
llm_response: ERB::Util.html_escape(result),
automation_name: ERB::Util.html_escape(automation&.name.to_s),
plugins/discourse-ai/lib/personas/tools/flag_post.rb 和 plugins/discourse-ai/lib/agents/tools/flag_post.rb 中实施了等效修复。本文不复现任何可利用 payload;公开的补丁 diff 是防御方的权威参考。
用户交互(CVSS 向量中的 UI:P)指的是管理员打开队列 —— 这正是其岗位职责。这就是为什么这个 CVSS 5.1 的”中等”评分在实践中比看起来更重:触发条件是审核工作的正常流程。
为什么重要
有两种模式让这个 CVE 值得超越 Discourse 本身去关注。
第一是信任边界错误。Discourse AI 插件把 LLM 当作内部、可信的组件 —— 与服务端模板同等对待 —— 因此其输出被当作标记渲染。但 LLM 的输出是攻击者可控输入的函数。一份 LLM 响应中的每一个字节都继承了其上下文中最具对抗性 token 的信任级别。正确的心智模型正是 OWASP LLM 项目在 LLM05 中强调的:LLM 是一个不可信用户,其输出必须在每一个下游 sink(HTML、SQL、shell、文件路径、URL)处进行清洗。Discourse 的代码在帖子正文路径上其实是谨慎的 —— Markdown 通过白名单进行清洗;只是 AI 功能简单地跳过了这一步,因为数据”来自我们自己”。
第二是 AI 分诊功能普遍带来的审核员即目标翻转。任何包含”LLM 为管理员审核用户内容”流程的产品都继承了同样的投递载体:攻击者撰写的内容,会被管理员的工具在管理员自己的会话内渲染。同样的模式已经在客服 copilot、安全编排工具的 AI 摘要器和 SOC 工单分诊中出现。如果你的应用有一个 LLM 把不可信输入摘要到面向管理员的 UI,你就具备了这一类漏洞的前提条件。审计这些渲染路径应视为 P1 优先级。
防御
Discourse 交付的修复只是底线,而非上限。一般性教训远不止一个插件。
-
在每一个 HTML sink 处转义 LLM 输出。 将
htmlSafe、dangerouslySetInnerHTML、v-html、bypass_sanitize等等视为需要书面理由才能使用的安全敏感原语。对于 LLM 输出尤其如此,安全的默认值是框架标准的 HTML 转义(ERB::Util.html_escape、Rails.html_safe?检查、React 的默认文本渲染、Django 的 autoescape)。如果需要来自 LLM 的富文本,请将其通过白名单清洗器(Sanitize gem、DOMPurify、bleach)—— 永远不要原样使用。 -
启用 Content Security Policy 并收集违规报告。 Discourse 自 2.2 起默认启用 CSP,实质性地约束了注入到审核队列的
<script>能做的事情。对于任何暴露给 AI 的管理 UI,严格的 CSP —— 不带unsafe-inline且配置report-uri—— 是 sink 漏过时的第二道防线。CSP 违规报告也提供了 SentinelOne 文档 强调的检测信号。 -
约束 LLM 输出的形态。 AI 分诊不需要输出 HTML。请强制模型产生结构化输出 —— JSON schema、Pydantic / Zod 校验、OpenAI structured outputs、Anthropic tool-use —— 并在响应到达渲染器之前拒绝任何包含 HTML 或标记 token 的内容。一个用允许字符正则进行校验的
reason: string字段,就足以在模板层上游消灭这个 CVE。 -
对每一条”LLM 为管理员摘要用户内容”的路径建模威胁。 盘点产品中的 AI 功能,列出 LLM 输入源(用户帖子、邮件、工单、网页抓取),列出渲染 sink(管理面板、Slack 通知、邮件摘要、SIEM 富化)。每一对(源, sink)都是同类漏洞的候选。把它画出来,确定清洗契约,并写一个回归测试,断言形似 payload 的字符串能以纯文本而非标记的形式往返。
-
打补丁并轮换会话。 如果你运行的 Discourse 启用了 AI 插件,请升级到任一已修复版本(
2026.3.0-latest.1、2026.2.1、2026.1.2)。如果升级延后,公告中的临时缓解措施是禁用 AI 分诊自动化脚本。如果在此期间打开过任何被举报的帖子,应将补丁前签发的管理员会话令牌视为可能已暴露 —— Discourse 的 GHSA 页面描述了轮换流程。 -
看补丁,而非分数。 CVSS 5.1 反映的是低权限前置条件和所需的用户交互,两者都是审核流程固有的约束。真实的影响半径是”任何履行职责的管理员都会打开队列”,这更接近会话劫持,而非典型的中等严重度 XSS。请把修复补丁 ——
44b84439、8ae7cb24、ed70949f—— 作为对自己代码库中 LLM 输出渲染路径进行 grep 式审查的参照。
状态
| 项目 | 参考 | 日期 | 备注 |
|---|---|---|---|
| CVE 公开 | NVD | 2026-03-19 | CVE-2026-27740 |
| GitHub 安全公告 | Discourse | 2026-03-19 | GHSA-95hc-42c6-wvvr |
| 已修复版本 | Discourse | 2026-03 | 2026.3.0-latest.1、2026.2.1、2026.1.2 |
| CVSS 4.0 | NVD | 2026-03-19 | 5.1 MEDIUM、CWE-79 |
| 受影响代码路径 | 修复补丁 | 2026-03 | llm_triage.rb、flag_post.rb(personas + agents) |
| 缓解措施 | 公告 | 2026-03-19 | 禁用 AI 分诊 |
CVE-2026-27740 的正确总结不是”Discourse 出了一个 XSS 漏洞”—— 那部分很普通。而是”一个 LLM 被当作可信的服务端组件,其输出经过了 htmlSafe”。同样的信任错误存在于任何允许模型写入面向管理员渲染路径的产品中。Discourse 的补丁是一个有用的试金石:如果你无法在自己的代码库中指出哪一行代码在 LLM 输出到达浏览器之前对其进行 HTML 转义,你就还没有完成对这类漏洞的修复。
Sources
- → https://nvd.nist.gov/vuln/detail/CVE-2026-27740
- → https://github.com/discourse/discourse/security/advisories/GHSA-95hc-42c6-wvvr
- → https://github.com/discourse/discourse/commit/44b84439df7e4424b2e7f216fd8fdd7dacff2227
- → https://www.sentinelone.com/vulnerability-database/cve-2026-27740/
- → https://genai.owasp.org/llmrisk/llm05-improper-output-handling/