Seafile SeaDoc:通过公开分享链接实现 RCE(CVE-2026-129167)
匿名 SeaDoc 分享令牌可到达转换器的所有端点,绝对路径会植入并执行一个 Python 包;已在 2.0.11 和 3.0.8 修复。
TL;DR
- SeaDoc 的 Confluence 导入中的绝对路径让公开
.sdoc分享链接的匿名持有者把 Python 包写入转换器的库路径并执行。Haiwen 于 2026 年 11 月 2 日修复了服务器。 - SeaDoc 2.0.4 至 2.0.10 和 3.0.0 至 3.0.7 受影响。实例必须有一个公开
.sdoc分享链接或已发布的 wiki。 - CVE 记录将其评为 CVSS v4.0 9.2 Critical。没有报告利用,截至 2026 年 11 月 14 日也不在 CISA 的 Known Exploited Vulnerabilities(KEV)目录中。
- 升级到 SeaDoc 2.0.11 或 3.0.8,或更高版本。转换器提交和缓解措施见“如何修复?”。
我受影响吗?
以下条件全部成立时,您会受影响:
- 部署运行 SeaDoc 2.0.4 至 2.0.10,或 3.0.0 至 3.0.7。
- 实例有一个公开
.sdoc分享链接或已发布的 wiki。分享页面嵌入的匿名令牌就是攻击使用的凭据。 - SeaDoc 转换器可通过 Seafile 前端访问。官方 Docker Compose 部署将其暴露在
/sdoc-server/converter/下。 - 部署是在 2026 年 11 月 2 日之前完成的,Haiwen 称当天修复了闭源
sdoc-server组件。
没有公开 .sdoc 分享链接也没有已发布 wiki 的部署不会暴露本文描述的匿名令牌路径。能获取 SeaDoc 令牌的已登录用户仍可到达转换器。转换器接受任何有效的 SeaDoc 令牌,因此为一个文档签发的令牌可到达转换器的所有端点。请把前提条件视为攻击路径的条件,而不是安全的默认状态。
| 分支 | 受影响版本 | 修复版本 |
|---|---|---|
| SeaDoc 2.0 | 2.0.4 至 2.0.10 | 2.0.11 |
| SeaDoc 3.0 | 3.0.0 至 3.0.7 | 3.0.8 |
撰写时的当前镜像为 2.0.13 和 3.0.9。两者都包含修复。
检查正在运行的镜像和公开分享:
# Image tag of the running SeaDoc container
docker inspect --format '{{.Config.Image}}' <seadoc-container>
# Images present on the host
docker image ls | grep sdoc-server
# Public links, in the Seafile web interface
# Open each library, then Sharing, and review the links that are public
对于官方部署,镜像标签是可靠的版本检查。compose 文件可能固定 2.0-latest 或 3.0-latest 等标签,这些标签可能在运行容器版本不变的情况下移动。
如何修复?
将 SeaDoc 升级到 2.0.11 或 3.0.8,或当前的 2.0.13 或 3.0.9,并重新创建容器。
- 拉取修复后的镜像并更新 compose 文件中的标签。
- 重新创建
seadoc和sdoc-server容器。只拉取镜像而不重新创建容器会让旧进程继续运行。 - 使用
docker inspect确认新标签。 - 检查公开
.sdoc链接和已发布 wiki,删除不需要的。 - 如果无法升级,在反向代理处阻止转换器端点,或删除所有公开
.sdoc链接并取消发布所有 wiki。缓解措施移除了匿名令牌路径,但不修复路径遍历。
开源 seadoc-converter 的修复是 2026 年 10 月 29 日的提交 cb43162e。它拒绝不是字符串、不以 .html.zip 结尾或包含 /、\ 或 NUL 字节的 filename。它还拒绝空的 space_key、. 和 ..。
sdoc-server 组件是闭源的。Haiwen 于 2026 年 11 月 2 日通过电子邮件告诉研究者 SeaDoc 2.0 和 3.0 已修复。没有公开提交或公告描述这一半修复。
根本原因是什么?
转换器只检查 SeaDoc JWT 的签名就接受任何令牌,然后把攻击者控制的文件名用作 ZIP 解压的绝对路径,并在上传挂起期间保留已解压的文件。
SeaDoc 有三个进程:seahub 负责认证和页面渲染,sdoc-server 负责协同编辑,seadoc-converter 负责文档转换。三者共享 HMAC 密钥 JWT_PRIVATE_KEY。访问者打开公开 .sdoc 分享链接时,seahub 会在页面中嵌入 JWT,以便匿名预览。
转换器的 check_auth_token() 用 config.SEADOC_PRIVATE_KEY 验证签名并返回 true。它从不检查令牌签发给哪个文档,也不检查令牌携带的权限:
def check_auth_token(req):
auth = req.headers.get('Authorization', '').split()
if not auth or auth[0].lower() != 'token' or len(auth) != 2:
return False
token = auth[1]
if not token:
return False
private_key = config.SEADOC_PRIVATE_KEY
try:
jwt.decode(token, private_key, algorithms=['HS256'])
except (jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
logger.exception(e)
return False
return True
匿名令牌是只读的,但转换器忽略 permission 字段。因此令牌可到达 /api/v1/confluence-to-wiki/,该端点接收 filename 并用 os.path.join 构造路径:
underscore_index = filename.rfind('_')
if underscore_index != -1:
space_key = filename[underscore_index + 1:-len('.html.zip')]
elif 'Confluence-space-export-' in filename:
wiki_name = filename[:-len('.html.zip')]
space_key = filename[len('Confluence-space-export-'):-len('.html.zip')]
else:
wiki_name = filename[:-len('.html.zip')]
space_key = wiki_name
try:
extract_dir = '/tmp/wiki'
space_dir = os.path.join(extract_dir, space_key)
zip_file_path = os.path.join(extract_dir, filename)
if not os.path.exists(extract_dir):
os.mkdir(extract_dir)
# Deploy on two machines
is_same_machine = True
if not os.path.exists(space_dir):
os.mkdir(space_dir)
is_same_machine = False
response = requests.get(download_url)
with ZipFile(BytesIO(response.content), 'r') as zip_ref:
zip_ref.extractall(space_dir)
当 os.path.join 的第二个参数以斜杠开头时,Python 会丢弃第一个参数。像 /usr/lib/python3.12/encodings/cp273 这样的 space_key 会让 space_dir 和 zip_file_path 变成绝对路径。转换器创建该目录并把攻击者的 ZIP 解压到那里。
清理只在 process_zip_file() 返回后于 finally 块中运行。该函数把结果发送到 upload_url,没有超时。接受连接但永不响应的上传目标会让请求一直阻塞,因此 finally 块不运行,植入的文件留在磁盘上。
如何被利用?
- 攻击者打开公开
.sdoc分享链接,从页面源码读取seadocAccessToken的值。该令牌是该文档的有效匿名凭据。 - 攻击者运行一个服务器,返回包含
__init__.py的 ZIP,接受连接但永不响应,并在被请求时返回带charset=cp273的 HTTP 500。 - 攻击者向
/sdoc-server/converter/api/v1/confluence-to-wiki/发送 POST,使用绝对filename(如/usr/lib/python3.12/encodings/cp273aaaaaaaaa)、提供 ZIP 的download_url和会挂起的upload_url。转换器把 ZIP 解压到 Python 的encodings包中。 - 攻击者向
/sdoc-server/converter/api/v1/file-convert/发送 POST,upload_url返回 500 和charset=cp273。转换器记录响应正文,Response.text用声明的 charset 解码它。 - CPython 延迟导入
encodings.cp273。FileFinder先检查目录包cp273/__init__.py,再检查模块cp273.py,因此植入的文件在转换器进程内运行。
研究者的公开分析包含两个请求和攻击者服务器。第一个请求在文件落盘期间阻塞,第二个请求触发导入。分析中的标记命令从转换器容器写入了 uid=0 (root)。
该路径不需要重启,因为导入在运行中的进程内按需发生。唯一前提是提供匿名令牌的公开 .sdoc 分享链接或已发布 wiki。
攻击如何展开?
匿名分享令牌通过仅验证签名的检查,绝对文件名把 Python 包写入转换器的库路径,charset 错误让 CPython 导入并运行它。
嵌入匿名 JWT"] --> B["转换器只检查
JWT 签名"] B --> C["confluence-to-wiki
在所有端点接受令牌"] C --> D["绝对 filename 让
os.path.join 写出 /tmp/wiki"] D --> E["ZIP 被解压到
Python encodings 包"] E --> F["挂起的上传阻止
finally 清理"] F --> G["charset=cp273 让 CPython
导入植入的包"] G --> H["代码在转换器
进程中运行"]
攻击者能做什么?
持有公开分享令牌的未认证攻击者可以在 SeaDoc 转换器进程中运行代码,官方容器以 root 运行该进程。
影响包括:
- 读取和修改转换器能到达的文档,包括其配置中的共享 HMAC 密钥。
- 使用该密钥为其他文档和服务伪造 SeaDoc 令牌。
- 到达转换器容器可访问的网络和挂载卷。
- 把转换器作为进入 Seafile 部署的立足点。
这些条件是影响的一部分:
- 实例必须暴露公开
.sdoc分享链接或已发布 wiki。 - 转换器必须可从攻击者网络访问。
- 部署必须是 2026 年 11 月 2 日之前构建的受影响 SeaDoc 版本。
- 攻击者不需要账户,也不需要用户交互。
研究者在官方 Docker Compose 实验中演示了 uid=0 (root)。代码在转换器容器中运行,默认不在 Seafile 应用服务器上。能否到达主机取决于容器的挂载和网络。
为什么修复有效?
转换器修复拒绝任何可能逃出 /tmp/wiki 的文件名,因此 os.path.join 不再能产生绝对解压路径。
修复提交 cb43162e 修改了 confluence_to_wiki() 开头的校验,并在派生 space_key 后增加检查:
- if not filename:
+ if (not isinstance(filename, str)
+ or not filename.endswith('.html.zip')
+ or any(char in filename for char in ('/', '\\', '\x00'))):
return {'error_msg': 'path invalid.'}, 400
...
else:
wiki_name = filename[:-len('.html.zip')]
space_key = wiki_name
+
+ if space_key in ('', '.', '..'):
+ return {'error_msg': 'path invalid.'}, 400
try:
extract_dir = '/tmp/wiki'
space_dir = os.path.join(extract_dir, space_key)
该检查拒绝 filename 中的斜杠、反斜杠和 NUL 字节,因此派生出的每个 space_key 都是单一路径组件。space_key 检查拒绝仍会指向普通子路径之外的三个值。解压保持在 /tmp/wiki 下。
这是开源转换器中的部分修复。阻止匿名只读令牌到达转换器所有端点的闭源 sdoc-server 更改没有公开。Haiwen 称该组件已修复;令牌范围更改无法审查。
如何检查是否已遭入侵?
没有公开的厂商入侵指标。检查针对两个转换器请求和植入 Python 库路径的文件。
- 在反向代理和 Seafile 访问日志中搜索发往
/sdoc-server/converter/api/v1/confluence-to-wiki/且filename以/开头或包含..的 POST 请求。 - 搜索发往
/sdoc-server/converter/api/v1/file-convert/且upload_url指向您基础设施之外的 POST 请求。 - 在转换器容器中,检查 Python
encodings目录是否有您未安装的包,尤其是以cp273等编解码器命名的目录。 - 检查
/tmp/wiki中遗留的目录和 ZIP 文件。 - 检查转换器容器的
/root、/tmp和应用目录中是否有意外文件。
研究者的概念验证写入 /root/pwned;攻击者可以写入其他内容。在解压前失败的请求可能不留痕迹,转换器只记录上传响应正文,不记录完整请求。
时间线
- 2026-10-24:研究者发布分析。
- 2026-10-29:编写
seadoc-converter修复提交cb43162e。 - 2026-11-02:Haiwen 告诉研究者闭源
sdoc-server组件已修复。 - 2026-11-13:CVE-2026-129167 于 17:21 UTC 发布。
常见问题
CVE-2026-129167 是否已在野利用?
没有报告利用。截至 2026 年 11 月 14 日,它不在 CISA 的 KEV 中,研究者的分析描述的是实验室复现,不是活动。
利用它需要账户吗?
不需要。攻击者需要公开 .sdoc 分享链接或已发布 wiki,并读取分享页面嵌入的匿名令牌。不需要 Seafile 账户。
匿名令牌会把攻击限制在一个文档吗?
不会。转换器只检查令牌签名。permission 和文档字段被忽略,因此匿名令牌可到达转换器的所有端点。
是 Seafile 服务器本身有漏洞,还是只有 SeaDoc?
缺陷在 SeaDoc 转换器和 SeaDoc 服务共享的令牌检查中。没有 SeaDoc 的 Seafile 部署不受此 CVE 影响。运行攻击者代码的是转换器容器。
哪个版本修复了它?
SeaDoc 2.0.11 和 3.0.8 是首批修复版本。当前的 2.0.13 和 3.0.9 也包含修复。闭源 sdoc-server 组件于 2026 年 11 月 2 日修复。
修复完整吗?
开源转换器的修复关闭了绝对路径遍历。闭源 sdoc-server 中的令牌范围更改没有公开,因此无法从代码审查其完整性。
参考文献
- CVE-2026-129167 记录,VulnCheck CNA,2026-11-13 发布,CVSS v4.0 9.2:https://www.cve.org/CVERecord?id=CVE-2026-129167(2026 年 11 月 14 日检查)
- Wu Yulun,“Seafile Vulnerability Report”,2026-10-24 发布,根本原因、复现和两个请求:https://vul.yulun.ac.cn/20261024/seafile/(2026 年 11 月 14 日检查)
seadoc-converter修复提交cb43162e,2026 年 10 月 29 日,文件名和space_key校验:https://github.com/haiwen/seadoc-converter/commit/cb43162e8bc2991a2900c8acf85090358a30e516(2026 年 11 月 14 日检查)- 易受攻击的
seadoc-converter源码bb6cf0f,seadoc_converter/server/apis.py,check_auth_token()和confluence_to_wiki():https://github.com/haiwen/seadoc-converter/blob/bb6cf0fb9ebc6bb05c9161acbfd6289737ec2cd6/seadoc_converter/server/apis.py(2026 年 11 月 14 日检查) - 易受攻击的
seadoc-converter源码bb6cf0f,seadoc_converter/converter/utils.py,无超时的上传:https://github.com/haiwen/seadoc-converter/blob/bb6cf0fb9ebc6bb05c9161acbfd6289737ec2cd6/seadoc_converter/converter/utils.py(2026 年 11 月 14 日检查) - Docker Hub,
seafileltd/sdoc-server标签,2.0.11、3.0.8、2.0.13 和 3.0.9:https://hub.docker.com/r/seafileltd/sdoc-server/tags(2026 年 11 月 14 日检查) - Docker Hub,
seafileltd/seafile-mc拉取次数,20,336,673:https://hub.docker.com/r/seafileltd/seafile-mc(2026 年 11 月 14 日检查) - CISA Known Exploited Vulnerabilities 目录,2026 年 11 月 14 日检查 CVE-2026-129167(未列出):https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json(2026 年 11 月 14 日检查)