0xmrma/CVE-2026-14361
GitHub: 0xmrma/CVE-2026-14361
该项目是 Consul Template CVE-2026-14361 路径重定向漏洞的详细分析与概念验证,揭示了 writeToFile 助手因未校验符号链接而导致的目录逃逸和文件覆盖问题。
Stars: 0 | Forks: 0
# CVE-2026-14361
Consul Template 的 `writeToFile` 助手直接打开了操作员提供的目标路径并跟随了链接的路径组件,导致渲染输出逃逸出预期目录并覆盖了已存在的文件。
## 简介
我在审查 **HashiCorp Consul Template** 时发现了这个问题,当时脑海中有一个直接的文件系统安全问题:
**如果 `writeToFile` 接收到的路径看起来在预期目录内,它是否会验证文件系统实际将数据写入到哪里?**
在本例中,答案是否定的。
`writeToFile` 模板助手通过 `os.Create()` 或 `os.OpenFile()` 直接打开了用户提供的最终路径。
这些操作会跟随目标路径中已存在的符号链接、目录联接和等效的文件系统重定向。
这意味着,虽然路径字符串可以保留在操作员的预期根目录下,但实际写入操作可能会落到其他地方。
在我受控的概念验证中,一个被链接的父级目录将渲染输出重定向到了预期树之外,并导致已存在的目标文件被覆盖。
该问题成为了 **CVE-2026-14361**。
**HashiCorp 公告:** [HCSEC-2026-20](https://discuss.hashicorp.com/t/hcsec-2026-20-consul-template-vulnerable-to-path-redirections-in-writetofile/77559)
**IBM 公告:** [CVE-2026-14361 安全公告](https://www.ibm.com/support/pages/security-bulletin-consul-template-vulnerable-path-redirection-writetofile-through-symlink-attack)
**CVE:** [CVE-2026-14361](https://www.cve.org/CVERecord?id=CVE-2026-14361)
**修复版本:** `0.42.1`
## 攻击链
`操作员提供的目标路径看似在预期根目录内 -> 攻击者预先布置链接的父级或最终路径组件 -> writeToFile 直接打开该路径 -> 文件系统将写入操作解析到预期目录之外 -> 渲染的机密被重定向 -> 已存在的目标文件可能被覆盖`
## writeToFile 的作用
Consul Template 渲染来自 Consul 和 Vault 等源的数据。
`writeToFile` 助手允许模板将选定的内容写入单独的本地文件,同时应用请求的所有者、组和权限模式。
HashiCorp 的文档特别演示了该助手处理 PKI 材料的场景:
```
private key -> writeToFile /my/path/to/cert.key
certificate authority -> writeToFile /my/path/to/cert.pem
certificate -> append to /my/path/to/cert.pem
```
这使得它不仅仅是一个普通的输出助手。
跨越此边界的内容可能包括:
- 私钥
- 证书
- 从 Vault 获取的机密
- 配置值
- 服务凭据
重要的问题不是 `writeToFile` 能否创建请求的文件名。
真正的问题是:
**进程是写入到操作员预期的文件系统位置,还是仅仅写入到路径在打开时所解析到的任何对象?**
在易受攻击的版本中,它信任的是后者。
## 为什么这个攻击面值得研究
写入助手是高价值的安全面,因为它们跨越了从应用程序数据到文件系统变更的边界。
有趣的故障通常不是经典的 `../` 遍历。
它们是解析失败:
- 字符串看起来是安全的
- 目录树看起来是由操作员控制的
- 链接的组件改变了实际目标
- open 调用自动跟随该重定向
当进程拥有比攻击者更高的文件系统权限时,这一点尤为重要。
低权限的本地攻击者可能无法直接覆盖敏感文件。
但是,如果他们能够影响预期写入目录下的路径组件,更高权限的 Consul Template 进程可能会替他们执行写入操作。
这就是我关注的边界。
## 我关注的边界
我并没有将此视为一般的路径遍历审查。
提供的路径不需要 `..` 段。
它在词法上可以一直保留在预期根目录内。
更深层的问题是:
对于以下两种情况,这个问题都至关重要:
- 链接的最终文件名
- 重定向整个剩余路径的链接目录组件
第二种情况特别有用,因为日志和配置仍然显示一个位于预期目录下、看似无辜的路径。
但文件系统会将其解析到其他地方。
## 根本原因
根本原因是在没有进行链接感知验证的情况下,直接进行基于路径的文件创建。
在测试的修订版本中,`writeToFile()` 选择了两种打开路径之一。
追加模式使用:
```
f, err = os.OpenFile(
path,
os.O_APPEND|os.O_WRONLY|os.O_CREATE,
perm,
)
```
正常写入模式使用:
```
dirPath := filepath.Dir(path)
if _, err := os.Stat(dirPath); err != nil {
err := os.MkdirAll(dirPath, os.ModePerm)
if err != nil {
return "", err
}
}
f, err = os.Create(path)
```
在文件被打开之前,这两种路径都没有拒绝被链接的目标组件。
这很重要,因为:
- `os.Create(path)` 会跟随现有的文件系统重定向并截断已解析的文件
- `os.OpenFile(path, ...)` 在追加模式期间会跟随链接的路径组件
- 链接的目录会改变最终文件名解析的位置
- 链接的最终组件可以将打开操作重定向到另一个现有的文件
这种基于路径的假设在写入之后仍然存在。
所有权和权限再次使用路径进行应用:
```
err = os.Chown(path, uid, gid)
err = os.Chmod(path, perm)
```
这意味着元数据操作也绑定到了可变的路径名,而不是已经打开的文件描述符。
### 为什么这是可利用的
因为攻击者可以在 `writeToFile` 运行之前准备好重定向。
基本的攻击不需要概率性的竞争条件。
其步骤非常直接:
- 操作员在预期根目录下配置目标路径
- 攻击者获得足够的本地访问权限,以在该写入位置下创建或替换链接组件
- 路径字符串看起来仍然停留在预期根目录下
- `writeToFile` 直接打开它
- 操作系统跟随链接或联接
- 渲染输出到达解析后的目标
- 如果该目标已经存在,正常的创建模式会截断并覆盖它
这就是整个漏洞。
## 为什么这是一个安全问题,而不仅仅是正常的符号链接行为
确实,操作系统通常会在基于路径的文件打开过程中跟随符号链接。
但这并不能使其成为安全的应用程序行为。
安全问题不是:
真正的问题是:
在易受攻击的版本中,它并没有。
这种区别很重要,因为 Consul Template 可能运行:
- 作为一个长期的服务
- 有权访问来自 Vault 的机密
- 在提权的账户下
- 拥有本地攻击者没有的直接写入权限
在这种情况下,跟随攻击者布置的文件系统重定向会产生真正的权限和信任边界问题。
## 概念验证
我围绕测试提交中确切的 `writeToFile` 行为构建了一个独立的复现程序。
受控设置包括:
- 一个预期的输出根目录
- 一个在该根目录之外的外部目录
- 预期根目录下的一个链接的父级组件
- 重定向目录中一个已存在的目标文件
- 传递给 `writeToFile` 的受控机密内容
复现流程如下:
1. 创建预期的输出根目录。
2. 创建一个独立的外部目标目录。
3. 在外部目录中放置一个已存在的目标文件。
4. 在预期根目录下创建一个链接的父级目录,使其解析为该外部目录。
5. 使用链接的父级构建最终目标路径,同时保持路径字符串位于预期根目录下。
6. 使用受控机密内容调用易受攻击的写入路径。
7. 解析并检查实际的目标。
8. 将最终文件字节和 SHA-256 与机密输入进行比较。
观察到的情况是:
- 预期的目标字符串保留在预期根目录下
- 链接的父级将实际写入重定向到该根目录之外
- `writeToFile` 跟随了重定向
- 已存在的外部目标被覆盖
- 最终的外部文件的 SHA-256 与机密输入完全匹配
这证明了声明的两个部分:
- 输出可以逃逸出预期的目录树
- 解析目标处的现有文件可能被覆盖
## 为什么这样选择 PoC
最终组件的符号链接已经可以证明不安全的链接跟随行为。
但是,链接的父级组件证明了一个更强大的操作点:
- 配置的文件名看起来可以完全正常
- 路径在词法上可以保留在预期根目录下
- 重定向可以发生在路径的更高位置
- 最终写入仍然可以落在其他地方
现有的目标也很重要。
如果没有它,PoC 将只显示意外的文件创建。
通过从一个现有的文件开始并验证其最终的哈希值,复现直接证明了覆盖行为。
这使得结果比仅仅基于源码的声明更加具体。
## 利用要求和范围
此问题需要本地文件系统的影响。
攻击者需要足够的访问权限,以便在预期写入位置或其下方创建或修改符号链接、目录联接或等效重定向。
Consul Template 进程随后必须通过该路径进行写入。
影响在很大程度上取决于进程权限和渲染的内容。
实际结果包括:
- 将模板输出重定向到操作员的预期目录之外
- 将敏感的渲染数据放置在攻击者可读的位置
- 覆盖解析目标处已存在的文件
- 当进程以高权限运行时,影响会增加
- 当内容包含密钥、证书或机密时,机密性风险会增加
这不是默认安装中远程未经身份验证的任意写入。
但这是一个明确的本地信任边界故障,在特权或共享文件系统部署中具有重大影响。
## 严重性和分类
HashiCorp 将此问题归类为:
- **CWE-59**: 文件访问前不当的链接解析
- **CVSS 3.1:**
```
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
```
发布的基准分数为:
```
4.7 / Medium
```
该向量反映了:
- 本地攻击者访问权限
- 影响路径所需的低权限
- 高攻击复杂性
- 无需用户交互
- 当敏感的渲染输出被重定向时,潜在的极高机密性影响
官方公告还明确记录了重定向写入可能会覆盖现有文件。
评分之所以为中等,是因为利用该漏洞取决于本地的路径控制条件,而不是因为这种文件系统影响是理论上的。
## 受影响的版本
HashiCorp 公告列出了:
```
Affected: consul-template up to and including 0.42.0
Fixed: consul-template 0.42.1
```
该修复程序已于 **2026 年 7 月 8 日** 在 `0.42.1` 版本中发布。
## 修复分析
`0.42.1` 补丁在多个层面对写入路径进行了强化。
### 1. 拒绝链接的目标组件
修补后的助手使用 `os.Lstat()` 检查:
- 直接父目录
- 最终目标组件
并拒绝这些组件是链接的情况。
这封堵了报告和回归测试中所涵盖的直接父级重定向和最终文件符号链接的情况。
### 2. 在支持的系统上为最终打开使用 `O_NOFOLLOW`
在受支持的 Unix 平台上,目标使用 `O_NOFOLLOW` 打开。
这使得如果在预检查和打开操作之间最终组件变成了符号链接,打开操作本身将会失败。
这很重要,因为单纯的预检查可能会创建另一个 TOCTOU 窗口。
由于 Windows 上无法通过相同的机制使用 `O_NOFOLLOW`,因此特定于平台的实现在该系统上相当于无操作。
### 3. 通过打开的描述符应用元数据
该补丁用基于描述符的操作替换了基于路径的所有权和模式操作:
```
f.Chown(uid, gid)
f.Chmod(perm)
```
这将元数据更改绑定到实际打开的文件上,而不是稍后再次解析路径。
### 4. 在意外的 stat 错误时关闭故障通道
现在,只有当 stat 失败确实是 `os.IsNotExist` 时,助手才会创建父目录。
诸如权限失败之类的其他错误将被返回,而不是继续进入写入路径。
### 回归测试覆盖范围
该补丁增加了针对以下情况的针对性测试:
- 拒绝最终组件符号链接
- 拒绝链接的父目录
- 正常路径成功执行
- 拒绝追加模式下的最终符号链接
- 验证敏感目标保持不变的字节对比
## 一个重要的修复边界
公开的补丁讨论清楚地记录了一个重要的限制。
新的验证检查:
- 直接父目录
- 最终的路径组件
它不会遍历并拒绝每一个更高层级的祖先组件。
维护者选择这一边界,是因为 `writeToFile` 没有配置沙箱根来锚定完整的包含检查,而且常见的操作系统可能在路径前缀中包含合法的受管链接,例如 macOS 上的 `/var -> /private/var`。
`O_NOFOLLOW` 也只保护最终组件,而不是每一个祖先目录。
这不会改变已报告漏洞的状态或官方修复版本。
它确实澄清了该补丁所提供的确切安全属性:
- 所报告的直接父级和最终组件重定向路径已被拒绝
- 元数据操作绑定到已打开的描述符上
- 该助手不声称提供针对每个祖先路径的通用文件系统沙箱
这种区别值得在技术文档中保留。
## 披露
我于 **2026 年 3 月 21 日** 作为第二个独立的 Consul Template 发现,将此问题私下报告给了 HashiCorp Security。
该报告包括:
- 源代码级别的根本原因分析
- 独立的概念验证
- 运行时输出
- 预期和解析后的路径证据
- 文件变更前后的行为
- 重定向目标与机密输入匹配的 SHA-256 确认
- 受影响的修订版本详细信息
最初的跟进邮件并不在安全团队的报告队列中,这很可能是因为它被邮件列表的垃圾邮件过滤器拦截了。
在我重新转发了完整的报告后,HashiCorp 与工程团队取得了联系,并将其作为独立于第一个 Consul Template 问题的事件进行了调查。
HashiCorp 在 `0.42.1` 中修复了该漏洞,并于 **2026 年 7 月 8 日** 发布了 **HCSEC-2026-20**。
IBM 针对相同的 CVE 发布了相应的安全公告。
两份官方公告都将报告人确认为:
**Mohamed Abdelaal (0xmrma)**
## 这个漏洞真正教给我们的
关键教训很简单:
每当特权代码写入受攻击者影响的路径时,这种区别就至关重要检查字符串是否以预期目录开头是不够的。
即使是没有任何遍历序列的干净路径,也可能通过以下方式解析到其他地方:
- 符号链接
- 目录联接
- 挂载重定向
- 可变的路径组件
敏感操作必须绑定到在正确边界处验证过身份的目标上。
这个问题也印证了一条更广泛的规则:
这正是基于描述符的 `Chown` 和 `Chmod` 更改至关重要的原因。
## 关键点
- `writeToFile` 在文档中被记录用于处理证书和私钥等敏感材料
- 易受攻击的版本直接使用 `os.Create` 或 `os.OpenFile` 打开目标
- 链接的父级和最终组件可能会重定向实际的写入操作
- 配置的路径可以保留在预期根目录下,而解析出的目标却在预期根目录之外
- 正常的创建模式可能会截断并覆盖现有的目标
- 基于路径的 `Chown` 和 `Chmod` 引入了额外的可变路径信任
- PoC 通过精确到字节的 SHA-256 比较证明了重定向和覆盖
- `0.42.1` 版本增加了链接检查、受支持系统上的 `O_NOFOLLOW`、基于描述符的元数据操作以及回归测试
## 结语
此漏洞与 `../` 遍历无关。
路径字符串看起来是正确的。
但文件系统目标并不正确。
Consul Template 接受了操作员的预期路径,跟随了攻击者布置的重定向,并将渲染的内容写入到了另一个文件中。
这就是它成为 **CVE-2026-14361** 的原因。
在 **consul-template 0.42.1** 中已修复。
标签:Go语言, 日志审计, 模板引擎, 漏洞分析, 程序破解, 系统运维, 路径探测, 路径遍历