安全扫描器也要被隔离:Trivy Terraform 文件读取漏洞与 CI 输出边界
一、已确认事实与证据边界
NVD 记录描述的受影响范围是 Trivy 0.71.0 之前版本,场景是对不可信输入执行 misconfiguration 扫描。第三方提交的 Terraform 配置可能通过文件系统函数访问扫描根目录上层的文件;风险还要求那里存在可读的敏感数据,且攻击者能看到包含该值的扫描结果。
官方变更记录在 0.71.0 (2026-06-01) 下明确列出 Terraform 文件系统函数路径穿越修复,并链接 PR #10664;版本发布页与其日期一致。因而最低修复版本可确认为 0.71.0,不是 10 月才出现的补丁。
本文未成功读取修复 PR 的完整差异,因此不声称厂商具体采用了哪一种路径解析 API。以下代码属于防御模型。现有材料不支持把文件读取夸大为任意命令执行,也没有确认在野利用。
二、背景:扫描不是纯粹的字符串匹配
基础设施即代码包含变量、表达式、模块和文件引用。扫描器为了理解配置,可能需要解析或求值部分内容。这提高了分析精度,也引入了新的资源边界:配置文件能否要求扫描器读取其他文件?读取操作采用哪个根目录?解析结果会在哪里显示?
**工程分析:**攻击者不一定需要获得执行 Shell 的能力。如果工具替其读取了 CI 工作目录附近的敏感文件,再自动将值写到外部贡献者可见的日志,机密性边界仍然可能失效。
应当同时检查三个条件,而不是看到依赖版本就直接判断已泄密:
| 条件 | 要核查的部署事实 |
|---|---|
| 输入可控 | 不可信提交是否能影响被扫描的 Terraform 配置 |
| 文件可达 | 扫描进程能否读取扫描范围外的敏感材料 |
| 输出可见 | 调用者是否能看到结果、日志、评论或制品 |
切断其中一个条件可以降低特定风险,但升级仍然必要;配置变化可能让先前不成立的条件重新成立。
三、"只读挂载"不等于"最小读取权限"
只读可以阻止修改,却不会阻止读取。将宿主机主目录只读挂载给扫描容器,仍可能扩大凭据暴露面。类似地,把整个工作空间挂载进去再告诉工具"只扫描子目录",并不等于文件系统层已经禁止访问其他目录。
建议把扫描输入制作为一个独立目录或受控快照,只包含当前分析所需文件。扫描账户不持有发布凭据,也不继承前序构建步骤的长期令牌。若工具需要联网更新数据库,应将更新与不可信配置分析的权限需求分开设计。
这些属于纵深防护建议,不是对 Trivy 官方修复方式的描述。
四、安全实验:区分路径前缀与目录归属
下面示例只在临时目录创建无敏感内容的文本。它展示规范化后的目录归属检查,不调用 Trivy,不解析 Terraform,不访问真实凭据。
python
from pathlib import Path
from tempfile import TemporaryDirectory
def resolve_inside(root, name):
root = root.resolve()
relative = Path(name)
if relative.is_absolute():
raise ValueError("absolute path rejected")
target = (root / relative).resolve(strict=True)
if not target.is_relative_to(root):
raise ValueError("outside scan root")
if not target.is_file():
raise ValueError("regular file required")
return target
with TemporaryDirectory() as tmp:
base = Path(tmp)
root = base / "input"
root.mkdir()
(root / "ok.txt").write_text("demo", encoding="utf-8")
(base / "outside.txt").write_text("not a secret", encoding="utf-8")
assert resolve_inside(root, "ok.txt").name == "ok.txt"
for candidate in ["../outside.txt", str(base / "outside.txt")]:
try:
resolve_inside(root, candidate)
except ValueError:
pass
else:
raise AssertionError(candidate)
print("3 checks passed")
为什么不用 str(target).startswith(str(root))?因为目录 input-backup 可能拥有相同字符串前缀,却不在 input 内。目录归属必须按路径结构判断。
这个模型适用于静态、受控的实验目录,不是对抗并发文件修改的通用沙箱 。在检查与打开之间,其他进程仍可能替换文件或符号链接。高风险执行环境应结合文件系统隔离、不可变输入及平台提供的目录相对安全打开机制,不能把一次 resolve() 当作所有竞态的解答。
五、输出也是数据通道
许多团队只关注扫描入口,却忽视了报告发布权限。扫描结果可能进入 CI 日志、PR 评论、SARIF、工单或归档制品。输出的读者范围可能远大于运行扫描的团队。
对不可信输入任务,建议默认不回显文件内容和环境变量,只输出必要的规则编号、脱敏路径和位置。若发现疑似敏感值,应在受控渠道处理;不要为了证明问题,再把原文粘贴到公开 PR。
历史排查也应遵循这一原则:先保全任务标识、执行版本、输入提交和报告访问记录,再用权限受控的方式检查日志。只有确认具体材料暴露后,才能制定对应的凭据轮换范围。没有异常日志不能证明从未暴露,发现旧版本也不能证明攻击已经发生。
六、修复和防护行动
**P0:**核验 CI 真正运行的 Trivy 二进制或镜像,更新到 0.71.0 或包含修复的受支持版本。不要只更新开发机。暂时无法升级时,停止让不可信配置在可读取秘密的环境中分析,并收紧结果可见范围。
**P1:**建立输入目录、挂载目录、运行身份与输出接收者清单。使用不含真实秘密的标记文件验证目录边界;测试外部提交场景,而不只测试可信主分支。
**P2:**把扫描器纳入供应链资产和升级基线;对解析器、格式转换器及报告模板统一开展资源能力评审。安全工具应接受与业务工具相同的最小权限约束。
总结
扫描器越能理解配置,越需要明确它被允许读取什么。安全任务的身份并不天然可信。输入目录隔离、工具升级和输出访问控制共同决定了 CI 扫描是否真正安全。