Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-34212 — Docmost 接受了一个附件节点内的 javascript: URL,将其通过存储和渲染保留下来,并在 Docmost 源中将其变回可点击的锚点。 | Kitploit
工具/GitHubGitHub/0xmrma/cve-2026-34212
漏洞分析漏洞利用Web安全渗透测试论文与研究学习与教育
GitHub0xmrma/cve-2026-34212

CVE-2026-34212

Docmost 接受了一个附件节点内的 javascript: URL,将其通过存储和渲染保留下来,并在 Docmost 源中将其变回可点击的锚点。

查看仓库
12个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-34212

Docmost 接受了一个包含 javascript: URL 的附件节点,将其原样存储并通过渲染后,在 Docmost 源域中将其恢复为可点击的锚点。

简介

我识别、负责任地披露并复现了 Docmost(一个开源协作文档平台)中的一个高风险存储型 XSS 问题。

Docmost 官方站点将其定位为企业级本地部署 Wiki,拥有 3M+ 下载量,并声称被包括 维尔纽斯市政府、Bechtle、澳大利亚政府、红十字会 和 ETS Quebec 在内的组织团队所信赖。

该漏洞位于富文本系统中一个容易被忽视的地方:

不在普通的链接扩展中,而在一个用于文件附件的独立自定义节点类型中。

我当时带着一个非常具体的问题审查编辑器管道:

如果普通链接会拦截 javascript: URL,那么附件节点在到达锚点 sink 之前是否执行相同的规则?

在受影响版本中,它们没有。

Docmost 接受了页面 JSON 中的恶意附件节点,原样存储了其 url 属性,随后将该值渲染回一个可点击的 <a href="javascript:..."> 元素。

该问题被分配为 CVE-2026-34212。

Docmost: docmost/docmost
安全公告: GHSA-cf68-cff9-hq4w
CVE: CVE-2026-34212
修复版本: v0.71.0

photo0

攻击链

攻击者控制的附件节点 URL -> 页面 JSON 被接受并原样存储 -> HTML/React 渲染将该 URL 转为锚点 href -> 受害者点击附件操作 -> 攻击者控制的 JavaScript 在 Docmost 源域中执行


Docmost 该部分的功能

Docmost 以兼容 ProseMirror/Tiptap 的 JSON 格式存储页面内容。

该内容模型包括用于以下内容的自定义块节点:

  • 图片
  • 图表
  • 嵌入内容
  • 附件

附件节点存储如下字段:

  • url
  • name
  • mime
  • size
  • attachmentId

服务器以多种格式接受页面内容:

  • json
  • markdown
  • html

并将其规范化为 ProseMirror JSON 后再存储。

这意味着任何可以携带 URL 的节点类型都属于直接信任边界的一部分。

如果这些节点类型中的某一种最终被渲染为 <a href>,那么 URL 方案处理就不是可选的。 它是安全模型的一部分。


为什么这个攻击面值得关注

自定义编辑器扩展是安全偏差的常见来源。

基础系统可能已经知道如何正确处理危险 URL,但每个自定义节点仍然需要在其自己的 sink 处重新应用相同的规则。

这就产生了一种可预测的审查策略:

  • 找到每个存储 URL 类似字段的节点类型
  • 追踪该字段在何处被接受
  • 追踪该字段在何处被渲染
  • 将其清理行为与平台正常的链接处理进行比较

这正是暴露此漏洞的方式。

Docmost 的普通链接扩展已经将 javascript: 视为危险。

而其附件节点却没有。

一旦你看到这种不对称性,安全问题就变得显而易见:

能否持久化一个 url 为 javascript: 的附件节点,并将其渲染回一个活动的锚点?

答案是肯定的。


根本原因

根本原因是不同内容节点类型之间 URL 清理不一致。

服务器端内容路径接受任意附件 URL,只要整体内容符合 ProseMirror 模式。

在受影响版本中:

  • CreatePageDto 接受 content?: string | object
  • PageService.parseProsemirrorContent() 将 markdown、html 或 json 规范化
  • 然后服务器调用 jsonToNode(prosemirrorJson)
  • 如果模式验证通过,则存储内容

该验证步骤检查的是结构有效性,而非 URL 安全性。

受影响服务器逻辑的关键部分实际上是这样:

root@kitploit:~
prosemirrorJson = content;
jsonToNode(prosemirrorJson);
return prosemirrorJson;

这里没有进行附件 URL 方案的规范化。

随后,附件扩展直接渲染攻击者控制的值。

受影响的附件节点做了如下操作:

root@kitploit:~
url: {
  default: "",
  parseHTML: (element) => element.getAttribute("data-attachment-url"),
  renderHTML: (attributes) => ({
    "data-attachment-url": attributes.url,
  }),
},

然后:

root@kitploit:~
[
  "a",
  {
    href: HTMLAttributes["data-attachment-url"],
    class: "attachment",
    target: "blank",
  },
  `${HTMLAttributes["data-attachment-name"]}`,
]

在客户端,React 节点视图再次将其包裹:

root@kitploit:~
<a href={getFileUrl(url)} target="_blank">

但 getFileUrl() 只对以下情况进行特殊处理:

  • 绝对 http URL
  • /api/...
  • /files/...

其他任何内容都原样返回。

因此,一个如下 payload:

root@kitploit:~
javascript:alert(document.domain)

逃脱了:

  • JSON 存储
  • 服务器端模式验证
  • HTML 渲染
  • 客户端 URL 处理

仅凭这一点就足以构成存储型 XSS。

使根本原因更为清晰的是对比点。

Docmost 的普通链接扩展显式阻止了 javascript::

  • 在 parseHTML() 中拒绝了 javascript:
  • 在 renderHTML() 中清除了 javascript: href

因此,产品已经知道该方案是危险的。

附件节点只是没有应用相同的策略。

这就是为什么这不是“编辑器中的通用 XSS”。

而是一个特定于节点的信任边界缺口。


为什么这是一个安全问题,而不仅仅是缺少清理

这个 bug 不仅仅是关于不安全的 HTML 美学。

它允许可以编辑页面的攻击者持久化一个恶意 payload,当其他用户与渲染后的附件交互时,该 payload 将在 Docmost 源域中执行。

这一点很重要,因为源内脚本可以:

  • 读取受害者可以访问的数据
  • 以受害者身份发出经过身份验证的请求
  • 修改受害者被允许修改的内容
  • 滥用暴露给会话的任何 DOM 或 API 界面

需要点击并不会将其降级为一个无关紧要的问题。

点击是产品正常行为的一部分: UI 有意将附件呈现为可操作的链接/图标。

因此,安全问题不是“攻击者能否在不进行任何交互的情况下强制执行任意 JS?”

真正的问题是:

应用程序是否存储了攻击者控制的包含脚本的内容,并随后将其作为可信交互路径呈现给其他用户?

在受影响版本中,它确实如此。

这就是存储型 XSS。


为什么利用是切实可行的

利用路径非常简单:

  • 任何具有页面编辑权限的用户都可以植入 payload
  • 恶意 URL 原样存储,未被修改
  • 页面正常渲染
  • 查看者只需要对页面具有标准访问权限
  • 单击附件操作就足以触发执行

这也使得高权限用户成为现实目标。

如果工作区所有者、管理员或广泛受信任的编辑器查看了攻击者控制的内容并点击了附件操作,那么攻击者的脚本将在该更高权限的会话上下文中运行。

这是重要的实际要点:

攻击者的权限要求仅仅是低的。 受害者的权限级别决定了 XSS 会话携带的价值。


概念验证

我针对 Docmost v0.70.3 进行了实体验证。

PoC 仅使用正常的 HTTP 请求和应用程序自身的页面 API。

流程如下:

  1. 作为可以编辑页面的用户登录。
  2. 创建或选择一个页面。
  3. 发送 POST /api/pages/update,其中 format: "json",并包含一个 url 为 javascript: payload 的附件节点。
  4. 通过 POST /api/pages/info 请求该页面。
  5. 确认存储的 JSON 仍然包含恶意 URL。
  6. 以 HTML 形式请求同一页面,确认服务器返回的锚点 href 仍然是 javascript:...。
  7. 在 UI 中,查看者点击渲染后的附件操作,将在 Docmost 源域中执行 payload。

最小的恶意内容如下:

root@kitploit:~
{
  "pageId": "<pageId>",
  "content": {
    "type": "doc",
    "content": [
      {
        "type": "attachment",
        "attrs": {
          "url": "javascript:alert(document.domain)",
          "name": "policy.pdf",
          "mime": "application/pdf",
          "size": 1
        }
      }
    ]
  },
  "operation": "replace",
  "format": "json"
}

我测试中观察到的实际结果是:

  • API 原样接受了恶意附件节点
  • 存储的页面 ID 为 019d18cf-4212-70b0-894a-fe20080fb0f1
  • POST /api/pages/info 返回的存储 JSON 中包含:
root@kitploit:~
"url": "javascript:alert(document.domain)"
  • POST /api/pages/info 以 format: "html" 返回的 HTML 包含:
root@kitploit:~
<div data-type="attachment" data-attachment-url="javascript:alert(document.domain)" data-attachment-name="policy.pdf" data-attachment-mime="application/pdf" data-attachment-size="1"><a href="javascript:alert(document.domain)" class="attachment" target="blank">policy.pdf</a></div>

该 HTML 响应是关键证据。

我无需依赖“浏览器可能会做一些有趣的事情”这种含糊的说法。

应用程序自身渲染了确切的执行 sink。

一旦用户点击该附件链接/图标,浏览器将在创建该页面的源域中执行 javascript: URL。


为什么选择这种 PoC 方式

对于编辑器驱动的 XSS,仅凭截图是不够的证据。

它们显示症状,而非边界失效。

这就是为什么我将 PoC 构建为两个明确的检查点:

  1. 存储证据
  2. 渲染 sink 证据

存储证据表明服务器接受并保留了危险方案。

渲染 sink 证据表明应用程序将该存储值恢复为:

root@kitploit:~
<a href="javascript:...">

这种区分很重要。

如果产品存储了危险输入但在每个 sink 之前将其中和,你可能有一个加固缺口,但不一定是活着的 XSS。

如果产品存储了危险输入并随后将其渲染为实际的执行 sink,你就拥有了完整的漏洞链。

这里正是如此。


修复分析

修复在 v0.71.0 中发布,通过对附件 URL 应用 URL 清理来解决渲染的利用路径。

附件扩展现在导入并使用 sanitizeUrl,包括:

  • 在解析时清理 data-attachment-url
  • 在渲染时清理 data-attachment-url
  • 清理锚点 href

从概念上讲,补丁将附件节点从:

  • 信任原始附件 URL
  • 发出原始附件 URL

改为:

  • 在附件 URL 成为渲染节点的一部分之前对其进行规范化

客户端助手 getFileUrl() 也已更新,未知方案不再原样通过。 在补丁版本中,回退路径返回 sanitizeUrl(src) 而不是直接返回 src。

这是修复的重要部分,因为受影响的代码有两个相互强化的问题:

  • 节点渲染原始 href
  • 客户端回退将未知方案视为可接受

补丁移除了这两个假设。

这对于活着的 XSS 路径来说是一个好的修复,因为它使附件 URL 处理与编辑器的其他安全模型保持一致。

尽管如此,还有一个更广泛的加固教训:

此处的客户端或渲染时清理是必要的,但在页面创建/更新时从服务器端拒绝危险方案将是更强的不变量。

最安全的长期模型是:

  • 在摄入时拒绝明显危险的方案
  • 在渲染边界再次进行清理

在富内容系统中,纵深防御很重要。


需要关注的回归场景

为了实现长期防护,以下是最需要关注的场景:

  • 包含 attachment.attrs.url = "javascript:..." 的 JSON 页面更新
  • 包含 data-attachment-url="javascript:..." 的 HTML 导入
  • 附件渲染绝不能输出 href="javascript:..."
  • 客户端回退助手不能未更改地返回未知的可执行方案
  • 附件节点和普通链接节点应共享等价的 URL 方案策略
  • 安全的内部附件路径(如 /api/files/... 和 /files/...)应继续正常工作

关键点是一致性。

如果普通链接被清理,但自定义的 URL 承载节点没有,那么编辑器实际上并没有单一的 URL 安全策略。

它只有碎片,而碎片正是 XSS 漏洞的栖息地。


严重性和分类

已发布的安全公告将本问题分类为:

  • CWE-79:网页生成过程中输入中和不当
  • CVSS v3.1:
root@kitploit:~
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N

最终评分为 7.6 / 高。

这是一个合理的分类。

重要的属性是:

  • 低攻击者权限要求
  • 存储型 payload
  • 在 Docmost 源域中执行
  • 范围发生变化
  • 显著的机密性影响,因为脚本可以访问受害者可见的应用内数据

用户交互仍然是必需的,因为受害者需要激活附件链接/图标。 这就是为什么 UI:R 是正确的。

但一旦发生这种交互,安全边界已经在更早的地方被突破: 应用程序存储了一个危险方案并将其渲染回执行 sink。


披露

我通过 GitHub 安全公告私下报告了该问题,内容包括:

  • 根因分析
  • 活着的 HTTP PoC
  • 存储的 JSON 证据
  • 渲染的 HTML sink 证据
  • 已固定的可丢弃测试环境

该问题已被接受,分配了 CVE-2026-34212,并于 2026 年 4 月 14 日 发布。

公开公告目前列出:

  • 受影响版本:0.70.3
  • 修复版本:0.71.0

我的实体验证是在 v0.70.3 上进行的,与已发布的受影响版本一致。


这个漏洞实际教会了我们什么

主要的教训不仅仅是“清理 URL”。

每个人都知道这一点。

更有趣的教训是:

如果一个应用程序有一种安全的 URL 承载节点类型和一种不安全的 URL 承载节点类型,那么不安全的那个才是真正的策略。

富文本系统往往积累自定义扩展的速度快于积累安全审查的速度。

这正好造成了这种不对称性:

  • 标准链接路径是加固的
  • 附件路径被视为“内部”或“特殊”
  • 特殊路径悄然成为更简单的 XSS sink

这个漏洞也表明仅模式验证是不够的。

jsonToNode() 验证了内容是结构有效的 ProseMirror 数据。 但它没有证明内容可以安全渲染。

这是两个不同的问题。

当你将这些问题分开考虑时,安全审查会变得更加精准:

  • 此内容结构有效吗?
  • 此内容安全存储吗?
  • 此内容在每个 sink 处都可以安全渲染吗?

附件节点通过了第一个问题,但在第三个问题上失败了。

这就是存储型内容漏洞如何在其他方面结构良好的编辑器管道中存活下来的方式。


关键点

  • Docmost 在页面内容中原样接受附件节点 URL。
  • 服务器端页面验证检查的是 ProseMirror 模式形状,而非 URL 方案安全性。
  • 受影响的附件节点直接从攻击者控制的输入渲染 data-attachment-url 和锚点 href。
  • 客户端助手 getFileUrl() 未更改地返回未知方案。
  • 普通链接节点已阻止 javascript:,但附件节点没有。
  • 低权限编辑器可以一次植入 payload,并针对后续查看者。
  • 实体验证同时证明了存储持久性和渲染的执行 sink。
  • v0.71.0 中的修复为附件节点和客户端回退路径添加了 sanitizeUrl 处理。

结语

这个漏洞并非关于浏览器怪癖。

而是关于一个绕过应用程序自身 URL 安全假设的自定义内容节点。

Docmost 接受了攻击者控制的附件 URL,通过存储原样保留,然后将其渲染回应用程序源域中的一个活动锚点。

这就是它成为 CVE-2026-34212 的原因。

v0.71.0 中的补丁干净地关闭了活跃的 XSS 路径,但更广泛的教训值得铭记:

在编辑器密集型应用程序中,每个能够携带 URL 的自定义节点都是其自己的安全边界,并且必须像边界一样进行审查。

下载工具