外部安全研究团队 Accomplish 在 Cloudflare Containers 中发现了一项跨租户数据暴露漏洞:新工作负载可能接触到之前工作负载遗留在磁盘上的数据。Cloudflare 随后调查并修复了该问题。
这类漏洞不一定意味着攻击者突破了正在运行的容器。问题可能发生在更容易被忽视的生命周期边界上——旧实例已经退出,但它用过的存储又被分配给了另一个租户,而底层数据并未真正消失。
容器删除不等于数据消失
从控制平面看,删除容器通常只是结束进程、卸载文件系统并回收资源。但在存储层,以下操作含义完全不同:
- 删除文件:通常只移除目录项或更新元数据。
- 重新格式化:可能重建文件系统结构,却不覆盖所有数据块。
- 释放卷或虚拟磁盘:只是把资源归还给池。
- 覆盖底层块:主动改变原有数据,但在 SSD、稀疏文件和精简配置存储上仍可能受重映射影响。
- 销毁独立加密密钥:在密钥隔离正确的前提下,使旧密文无法恢复。
因此,多租户容器平台不能把“工作负载已删除”直接当成“数据已清除”。只要底层磁盘、快照、缓存层或块设备会被复用,资源分配器就必须提供明确的清洁性保证。
根据给出的事件摘要,可以确认问题与前一工作负载的残留磁盘数据有关;摘要没有披露具体存储实现、利用路径或 Cloudflare 最终采用的内部清理机制,因此不应把某一种文件系统或磁盘技术当成此次事件的既定根因。
调查重点不是一块盘,而是整个复用链路
排查此类问题时,只修补容器启动脚本往往不够。工程团队需要沿资源生命周期建立证据链:
- 数据如何进入磁盘:应用层文件、临时目录、交换空间、日志、崩溃转储和镜像层都应纳入范围。
- 资源如何被释放:异常退出、超时强杀、宿主机重启和控制平面故障是否会绕过清理逻辑。
- 磁盘如何进入资源池:释放后是立即复用、延迟清理,还是先进入隔离区。
- 新租户能够看到什么:除了已挂载文件系统,还要考虑未分配空间、额外块设备和错误挂载的旧快照。
- 影响范围如何界定:哪些资源池、时间窗口、区域和实例类型可能经过同一条复用路径。
日志也需要谨慎设计。为了调查而记录完整文件内容,可能制造第二份敏感数据。更安全的做法是记录资源标识、状态转换、清理结果、不可逆摘要和时间戳,而不是客户明文。
用最小实验理解“逻辑删除”风险
下面的 Linux 脚本用普通文件模拟一块可复用磁盘。它不是 Cloudflare 实现的复现代码,而是一个可以本地运行的残留数据实验:写入标记后执行“逻辑释放”,原始字节仍然可以被扫描出来;覆盖后标记才消失。
运行前只需要 bash、truncate、dd 和 GNU grep,不要把真实密钥或客户数据写入测试标记。
#!/usr/bin/env bash
set -euo pipefail
DISK="$(mktemp)"
trap 'rm -f "$DISK"' EXIT
MARKER="tenant-a-canary-$(date +%s)-$$"
DISK_SIZE_MB=16
OFFSET=$((4 * 1024 * 1024))
truncate -s "${DISK_SIZE_MB}M" "$DISK"
printf '%s' "$MARKER" | dd \
of="$DISK" bs=1 seek="$OFFSET" conv=notrunc status=none
echo "[1] Tenant A wrote a canary marker"
# 模拟只归还资源、不覆盖数据的释放过程。
true
if LC_ALL=C grep -aFq "$MARKER" "$DISK"; then
echo "[2] Unsafe reuse detected: old bytes remain readable"
else
echo "Unexpected result: marker was not found" >&2
exit 1
fi
# 仅用于演示覆盖效果,不代表适用于所有 SSD 或云存储后端。
dd if=/dev/zero of="$DISK" bs=1M count="$DISK_SIZE_MB" \
conv=notrunc status=none
if LC_ALL=C grep -aFq "$MARKER" "$DISK"; then
echo "Sanitization failed: marker is still present" >&2
exit 1
else
echo "[3] Marker absent after sanitization"
fi
生产环境中的回归测试可以沿用“金丝雀”思路,但应通过平台自己的分配 API 完成:测试租户 A 写入随机标记并释放资源,测试租户 B 随后申请资源,再验证 B 无法读取该标记。测试标记必须是随机生成的无敏感数据,并且测试只能在授权环境中运行。
修复要同时覆盖存量、增量和失败路径
针对残留数据风险,可以这样设计分层防线:
- 立即止血:暂停可疑资源池的跨租户复用,隔离尚未完成清理的磁盘。
- 重新建立清洁状态:对可能受影响的存量资源执行经过验证的清理、加密擦除或销毁重建。
- 改变分配协议:资源只有在清理完成并验证成功后,才能从隔离状态进入可分配状态。
- 采用租户级加密边界:为租户或短生命周期工作负载使用独立数据密钥,释放资源时销毁密钥。密钥复用或隔离错误会削弱这一保证。
- 默认失败关闭:清理任务超时、节点掉线或验证程序异常时,资源应继续隔离,而不是返回公共池。
- 持续检测:把跨租户金丝雀测试、资源状态审计和异常复用告警加入发布门禁。
单纯执行 rm -rf 或快速格式化通常不足以证明安全;对 SSD 或精简配置存储反复写零也未必覆盖所有物理位置。平台应根据实际后端选择可验证的介质清理方法,或者通过正确实现的加密擦除降低对物理覆盖的依赖。
上线前应回答的几个问题
Cloudflare Containers 事件提醒平台团队:隔离不仅存在于 CPU、内存和网络命名空间,也存在于资源被释放和重新分配的那一刻。上线多租户容器能力前,至少应确认:
- 新租户能否接触任何旧文件、未分配块、快照或交换数据?
- 正常退出与异常退出是否经过同一套安全清理流程?
- 清理失败时,资源是否自动进入隔离区?
- 团队能否根据审计记录界定受影响的资源和时间窗口?
- 存储清洁性是否有自动化回归测试,而不只是设计文档中的承诺?
真正可靠的边界不是“我们调用了删除命令”,而是“系统能够持续证明,上一位租户的数据不会交给下一位租户”。