0xmrma/CVE-2026-34207
GitHub: 0xmrma/CVE-2026-34207
该项目详细记录了 Typebot 平台 CVE-2026-34207 SSRF 漏洞的根因分析、PoC 复现及修复方案,展示了因 DNS 解析时序差导致 SSRF 防护绕过的完整过程。
Stars: 0 | Forks: 0
# CVE-2026-34207
SSRF 过滤器检查了主机名文本,但实际目的地随后由 DNS 决定。这一漏洞使得攻击者控制的 Webhook URL 能够访问环回地址、元数据和私有网络目标。
## 简介
我在审查 **Typebot**(一个开源的聊天机器人构建器)时发现了这个问题,当时我脑海中有一个简单的安全疑问:
**如果 SSRF 防护只验证了主机名文本,但真正的目的地随后由 DNS 决定,会发生什么?**
在这个案例中,这个问题引出了一个真实的漏洞。
Typebot 针对 `Webhook` / HTTP Request 模块的 SSRF 防护仅验证了:
- URL 字符串,
- 阻止了字面量主机名,
- 以及字面量 IP 格式
它**没有**在允许请求之前解析主机名。
这意味着像 `ssrf-repro.example` 这样的主机名在校验期间看起来是无害的,但随后会解析为:
- `127.0.0.1`
- `169.254.169.254`
- 或 RFC1918/私有网络地址空间
并且仍然会被后端 HTTP 客户端获取。
该问题最终成为了 **CVE-2026-34207**。
**Typebot:** [GitHub 上的 Typebot](https://github.com/baptisteArno/typebot.io)
**CVE:** CVE-2026-34207
**修复版本:** `3.16.0`
这影响了 **Typebot**,一个被广泛使用的开源聊天机器人平台。在其官方网站上,Typebot 显示受到**全球 650 多家公司**的信任。该网站还宣称拥有**每月 200 万以上的聊天量**和**150 万以上发布的机器人**。
## 攻击链
`攻击者控制的 Webhook URL -> 主机名通过仅限字面量的 SSRF 校验 -> 在放行决定前没有进行 DNS 解析 -> 后端 HTTP 客户端将主机名解析为内部目标 -> 服务端请求到达环回地址/元数据/私有网络 -> 响应数据可通过执行日志获取`
## Typebot 的作用
**Typebot** 是一个聊天机器人构建器。
它允许用户创建可以执行以下操作的流程:
- 提问
- 收集结构化输入
- 调用外部服务
- 串联业务逻辑
- 并通过 `Webhook` / HTTP Request 模块触发出站 HTTP 请求
这意味着出站请求的执行是一个真正的安全边界。
这里重要的问题不是 Typebot 是否支持 Webhook 模块。
真正的问题是:
**SSRF 防护验证的是服务器将要连接的实际目的地,还是仅仅验证了出现在 URL 中的主机名文本?**
在这个案例中,它一开始只校验了文本形式。
这就是错误所在。
## 为什么这个攻击面值得研究
SSRF 防御以非常可预测的方式失效。
大多数时候,有趣的错误不是:
- “你忘记阻止 `169.254.169.254.254` 了”
- 或 “你忘记阻止 `localhost` 了”
更深层的错误是边界错误:
- 校验发生在标准化之前
- 校验发生在重定向之前
- 校验发生在 DNS 解析之前
- 校验发生在一种表示形式上,但网络栈使用的是另一种
这正是这里值得深入研究的地方。
Typebot 已经有了针对字面量元数据 IP、环回地址、私有范围和编码 IP 技巧的 SSRF 加固逻辑。
这让下一个问题变得显而易见:
这正是所发生的情况。
## 根本原因
根本问题是**基于主机名文本而非解析后的 IP 地址进行目的地校验**。
在存在漏洞的实现中,`validateHttpReqUrl()`:
- 解析了 URL
- 仅允许 `http:` 和 `https:`
- 阻止了少量字面量主机名,如 `metadata.google.internal`、`metadata.goog`、`metadata` 和 `localhost`
- 检测了字面量十进制/十六进制/八进制 IP 技巧
- 解析了字面量 IPv4 / IPv6 地址
- 仅在主机名本身已经是字面量 IP 时才校验该地址
这是最重要的部分。
如果主机名是一个正常的值,比如:
`ssrf-repro.example`
那么 `parseIPAddress(hostname)` 会返回 `null`,校验器就到此为止了。
在批准之前没有发生任何 DNS 解析。
因此,存在漏洞的逻辑实际上简化为:
```
const ip = parseIPAddress(hostname);
if (ip) {
validateIPAddress(ip);
}
```
这意味着:
- 字面量的危险 IP 被阻止了
- 编码的危险 IP 被阻止了
- 但是解析为危险 IP 的主机名没有被阻止
该漏洞的另一半在执行路径中。
在 `executeHttpRequest()` 中,Typebot 首先进行校验,随后使用 `ky(request.url, ...)` 发起实际请求。
所以其顺序是:
- 校验 URL 字符串
- 接受主机名
- 随后在真实的出站请求期间解析主机名
- 连接到解析出的内部目标
这就是整个漏洞。
## 为什么这是一个安全问题,而不仅仅是过滤不完整
重要的区别在于**信任决策是在哪里做出的**。
如果你描述得不好,很多漏洞看起来都很小。
如果你把这个问题描述为:
这听起来像是一个质量问题。
这不是真正的问题。
真正的问题是:
- 服务器在知道真实目的地之前做出了安全决策
- 网络栈随后连接到了其他地方
- 并且应用程序将该请求视为有效
这不是表面上的过滤弱点。
这是一个信任边界的失败。
并且由于 HTTP 执行器将响应数据记录在执行日志中,该问题在最严重的情况下甚至不是盲目的。
所以这不仅仅是:
- “发生了意外的内部流量”
而是:
- 发生了内部流量
- 并且攻击者通常可以通过正常的应用程序行为恢复证据和响应内容
这是一个真实的 SSRF 漏洞。
## 概念验证
我使用了两个验证层,因为它们演示了不同的内容。
### PoC 1:独立的本地解析器演示
第一个 PoC 干净地隔离了根本原因。
我使用了一个小型的本地测试工具,它:
- 在 `127.0.0.1` 上启动了一个环回 HTTP 服务器
- 校验了一个诸如 `http://ssrf-repro.example:18080/...` 的 URL
- 使用了一个受控的解析器,使 `ssrf-repro.example` 解析为 `127.0.0.1`
- 然后执行了请求
这准确地演示了缺陷:
- 校验通过,因为主机名不是被阻止的字面量值
- 随后的请求仍然到达了环回地址
捕获的输出显示:
- 校验器结果:通过
- 请求执行结果:到达环回地址
- 从环回服务返回了响应体
这直接证明了校验差距。
### PoC 2:真实的 Typebot 执行路径
第二个 PoC 通过实际重要的特性路径展示了该漏洞。
最简单的重现方式是:
1. 在 Typebot 后端解析 DNS 的机器上,将一个无害的主机名映射到环回地址
2. 启动一个本地 HTTP 服务
3. 创建一个指向该无害主机名的 `Webhook` 模块
4. 通过正常的已认证预览或实时执行路径触发该模块
一个具有代表性的模块如下所示:
```
{
"id": "blk-webhook",
"type": "Webhook",
"options": {
"webhook": {
"method": "GET",
"url": "http://ssrf-repro.example:8000/"
}
}
}
```
使用类似以下的 hosts 文件条目:
```
127.0.0.1 ssrf-repro.example
```
校验器仍然接受了该 URL,因为:
- `ssrf-repro.example` 不在 `blockedHostnames` 中
- 它不是 `localhost`
- `parseIPAddress("ssrf-repro.example")` 返回了 `null`
- 没有进行任何目的地 IP 校验
然后后端 HTTP 客户端将主机名解析为 `127.0.0.1` 并照常连接。
这确立了完整的声明:
- 存在漏洞的校验逻辑在实际功能使用中是可到达的
- 请求命中了被阻止的目的地类别
- 并且应用程序仍然将其视为成功的出站 HTTP 请求
## 为什么选择这两个 PoC
第一个 PoC 证明了根本原因。
第二个 PoC 证明了产品影响。
这种区分很重要。
如果你只展示:
你展示的还不够。
如果你只展示:
你没有隔离出原因。
更有力的报告是:
- 基于主机名的校验接受了一个本不应该被信任的值
- 随后的 DNS 解析改变了该值的真实安全含义
- 后端连接到了一个被阻止的目的地
- 并且功能路径依然完成
这才是完整的故事。
## 严重性与分类
这个问题被合理地归类为 **High**。
公告分类为:
- **CWE-918**:服务器端请求伪造(SSRF)
- **CWE-20**:输入验证不充分
- **CVSS:**
```
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
```
这是合理的。
这本身并不是一个未经认证的、全互联网范围的 SSRF。
所需的权限为 **Low**,因为这个独立的漏洞需要一个能够配置或触发 `Webhook` / HTTP Request 模块的参与者。
但在该边界内,其影响是严重的:
- 环回访问权限
- 私有网络访问权限
- 元数据访问权限
- 以及通过日志进行的响应数据泄露
这本身就是一个严重的 SSRF 问题。
并且当与其他扩大可达性的漏洞结合使用时,它会变得更加危险。
## 为什么这仍然值得报告
有些人看到经过认证的 SSRF 就会立即低估它。
这是一个错误。
真正的问题不是:
真正的问题是:
在这里,答案是肯定的。
校验器声称要保护:
- 元数据服务
- 环回地址
- RFC1918 私有范围
- IPv6 本地范围
但是主机名解析的差距让这些相同的目的地通过另一种表示形式重新进入了访问范围。
这正是那种值得获得 CVE 的漏洞。
## 修复分析
这个修复很稳健,因为它纠正了真正的边界,而不仅仅是一个症状。
在修补后的实现中,`validateHttpReqUrl()` 被修改为在批准请求之前解析主机名。
校验器现在:
- 从 `node:dns/promises` 导入 `lookup`
- 将校验视为异步操作
- 在允许非字面量主机名之前对其进行解析
- 解析每个解析到的地址
- 针对相同的阻止范围校验每个解析到的地址
这是正确的修复,因为它将信任决策从:
- “主机名文本看起来没问题吗?”
改变为:
- “实际目的地是否解析到了被允许的地址?”
这是从一开始就应该存在的安全属性。
该修复还增加了针对以下情况的回归测试覆盖:
- 解析为环回地址的主机名
- 解析为 RFC1918 空间的主机名
- 面向自托管者的允许列表内部主机
- 保留对元数据和环回目标不可绕过的防护
这就是你在真正的 SSRF 修复中想要的加固:
- 边界得到了纠正
- 预期行为在代码中进行了记录
- 并且测试锁定了该类别
该问题已在 **Typebot 3.16.0** 中得到解决。
## 披露
该问题是通过 GitHub 的安全报告流程私下报告的。
报告包括:
- `validateHttpReqUrl()` 中的根本原因
- `executeHttpRequest()` 中的下游执行路径
- 使用主机名解析到环回/私有目标的实际重现策略
- 以及一个用于隔离校验差距的独立演示
维护者接受了该问题,修复了校验逻辑,随后该漏洞被发布为:
**CVE-2026-34207**
修复程序已在 **Typebot 3.16.0** 中发布。
## 这个漏洞实际上教会了我们什么
这里的关键教训很简单:
这听起来很明显。
但是很多 SSRF 防护正是由于没有遵循这条规则而失效。
- 主机名字符串看起来是安全的。
- DNS 改变了它的含义。
- 请求依然发送出去了。
这就足够了。
这个漏洞还强化了关于一般 SSRF 审查的一些重要事项:
- 字面量 IP 过滤是不够的
- 主机名黑名单是不够的
- 编码 IP 检查是不够的
如果你不解析并校验真实的最终目的地,你的 SSRF 过滤器就仍然是不完整的。
这才是真正的结论。
## 关键点
- 当校验发生在目的地解析之前时,SSRF 防御就会失效
- 主机名文本校验与目的地 IP 校验不是一回事
- Webhook / HTTP Request 功能是真正的安全边界
- 当经过认证的 SSRF 能够到达元数据和内部服务时,它仍然可以是高危漏洞
- 一份有力的报告能将根本原因与影响联系起来,而不是仅仅提及其中一个
- 这个修复是正确的,因为它将决策转移到了实际解析后的目的地
## 结语
这个漏洞不在于花哨的 payload。
它在于提出了正确的边界问题。
在 Typebot 中,SSRF 校验器首先检查了主机名文本。
实际目的地随后由 DNS 决定。
网络客户端遵循了解析到的地址。
这个差距就是漏洞。
这就是为什么它成为了 **CVE-2026-34207**。
已在 **Typebot 3.16.0** 中修复。
## 攻击链
`攻击者控制的 Webhook URL -> 主机名通过仅限字面量的 SSRF 校验 -> 在放行决定前没有进行 DNS 解析 -> 后端 HTTP 客户端将主机名解析为内部目标 -> 服务端请求到达环回地址/元数据/私有网络 -> 响应数据可通过执行日志获取`
## Typebot 的作用
**Typebot** 是一个聊天机器人构建器。
它允许用户创建可以执行以下操作的流程:
- 提问
- 收集结构化输入
- 调用外部服务
- 串联业务逻辑
- 并通过 `Webhook` / HTTP Request 模块触发出站 HTTP 请求
这意味着出站请求的执行是一个真正的安全边界。
这里重要的问题不是 Typebot 是否支持 Webhook 模块。
真正的问题是:
**SSRF 防护验证的是服务器将要连接的实际目的地,还是仅仅验证了出现在 URL 中的主机名文本?**
在这个案例中,它一开始只校验了文本形式。
这就是错误所在。
## 为什么这个攻击面值得研究
SSRF 防御以非常可预测的方式失效。
大多数时候,有趣的错误不是:
- “你忘记阻止 `169.254.169.254.254` 了”
- 或 “你忘记阻止 `localhost` 了”
更深层的错误是边界错误:
- 校验发生在标准化之前
- 校验发生在重定向之前
- 校验发生在 DNS 解析之前
- 校验发生在一种表示形式上,但网络栈使用的是另一种
这正是这里值得深入研究的地方。
Typebot 已经有了针对字面量元数据 IP、环回地址、私有范围和编码 IP 技巧的 SSRF 加固逻辑。
这让下一个问题变得显而易见:
这正是所发生的情况。
## 根本原因
根本问题是**基于主机名文本而非解析后的 IP 地址进行目的地校验**。
在存在漏洞的实现中,`validateHttpReqUrl()`:
- 解析了 URL
- 仅允许 `http:` 和 `https:`
- 阻止了少量字面量主机名,如 `metadata.google.internal`、`metadata.goog`、`metadata` 和 `localhost`
- 检测了字面量十进制/十六进制/八进制 IP 技巧
- 解析了字面量 IPv4 / IPv6 地址
- 仅在主机名本身已经是字面量 IP 时才校验该地址
这是最重要的部分。
如果主机名是一个正常的值,比如:
`ssrf-repro.example`
那么 `parseIPAddress(hostname)` 会返回 `null`,校验器就到此为止了。
在批准之前没有发生任何 DNS 解析。
因此,存在漏洞的逻辑实际上简化为:
```
const ip = parseIPAddress(hostname);
if (ip) {
validateIPAddress(ip);
}
```
这意味着:
- 字面量的危险 IP 被阻止了
- 编码的危险 IP 被阻止了
- 但是解析为危险 IP 的主机名没有被阻止
该漏洞的另一半在执行路径中。
在 `executeHttpRequest()` 中,Typebot 首先进行校验,随后使用 `ky(request.url, ...)` 发起实际请求。
所以其顺序是:
- 校验 URL 字符串
- 接受主机名
- 随后在真实的出站请求期间解析主机名
- 连接到解析出的内部目标
这就是整个漏洞。
## 为什么这是一个安全问题,而不仅仅是过滤不完整
重要的区别在于**信任决策是在哪里做出的**。
如果你描述得不好,很多漏洞看起来都很小。
如果你把这个问题描述为:
这听起来像是一个质量问题。
这不是真正的问题。
真正的问题是:
- 服务器在知道真实目的地之前做出了安全决策
- 网络栈随后连接到了其他地方
- 并且应用程序将该请求视为有效
这不是表面上的过滤弱点。
这是一个信任边界的失败。
并且由于 HTTP 执行器将响应数据记录在执行日志中,该问题在最严重的情况下甚至不是盲目的。
所以这不仅仅是:
- “发生了意外的内部流量”
而是:
- 发生了内部流量
- 并且攻击者通常可以通过正常的应用程序行为恢复证据和响应内容
这是一个真实的 SSRF 漏洞。
## 概念验证
我使用了两个验证层,因为它们演示了不同的内容。
### PoC 1:独立的本地解析器演示
第一个 PoC 干净地隔离了根本原因。
我使用了一个小型的本地测试工具,它:
- 在 `127.0.0.1` 上启动了一个环回 HTTP 服务器
- 校验了一个诸如 `http://ssrf-repro.example:18080/...` 的 URL
- 使用了一个受控的解析器,使 `ssrf-repro.example` 解析为 `127.0.0.1`
- 然后执行了请求
这准确地演示了缺陷:
- 校验通过,因为主机名不是被阻止的字面量值
- 随后的请求仍然到达了环回地址
捕获的输出显示:
- 校验器结果:通过
- 请求执行结果:到达环回地址
- 从环回服务返回了响应体
这直接证明了校验差距。
### PoC 2:真实的 Typebot 执行路径
第二个 PoC 通过实际重要的特性路径展示了该漏洞。
最简单的重现方式是:
1. 在 Typebot 后端解析 DNS 的机器上,将一个无害的主机名映射到环回地址
2. 启动一个本地 HTTP 服务
3. 创建一个指向该无害主机名的 `Webhook` 模块
4. 通过正常的已认证预览或实时执行路径触发该模块
一个具有代表性的模块如下所示:
```
{
"id": "blk-webhook",
"type": "Webhook",
"options": {
"webhook": {
"method": "GET",
"url": "http://ssrf-repro.example:8000/"
}
}
}
```
使用类似以下的 hosts 文件条目:
```
127.0.0.1 ssrf-repro.example
```
校验器仍然接受了该 URL,因为:
- `ssrf-repro.example` 不在 `blockedHostnames` 中
- 它不是 `localhost`
- `parseIPAddress("ssrf-repro.example")` 返回了 `null`
- 没有进行任何目的地 IP 校验
然后后端 HTTP 客户端将主机名解析为 `127.0.0.1` 并照常连接。
这确立了完整的声明:
- 存在漏洞的校验逻辑在实际功能使用中是可到达的
- 请求命中了被阻止的目的地类别
- 并且应用程序仍然将其视为成功的出站 HTTP 请求
## 为什么选择这两个 PoC
第一个 PoC 证明了根本原因。
第二个 PoC 证明了产品影响。
这种区分很重要。
如果你只展示:
你展示的还不够。
如果你只展示:
你没有隔离出原因。
更有力的报告是:
- 基于主机名的校验接受了一个本不应该被信任的值
- 随后的 DNS 解析改变了该值的真实安全含义
- 后端连接到了一个被阻止的目的地
- 并且功能路径依然完成
这才是完整的故事。
## 严重性与分类
这个问题被合理地归类为 **High**。
公告分类为:
- **CWE-918**:服务器端请求伪造(SSRF)
- **CWE-20**:输入验证不充分
- **CVSS:**
```
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
```
这是合理的。
这本身并不是一个未经认证的、全互联网范围的 SSRF。
所需的权限为 **Low**,因为这个独立的漏洞需要一个能够配置或触发 `Webhook` / HTTP Request 模块的参与者。
但在该边界内,其影响是严重的:
- 环回访问权限
- 私有网络访问权限
- 元数据访问权限
- 以及通过日志进行的响应数据泄露
这本身就是一个严重的 SSRF 问题。
并且当与其他扩大可达性的漏洞结合使用时,它会变得更加危险。
## 为什么这仍然值得报告
有些人看到经过认证的 SSRF 就会立即低估它。
这是一个错误。
真正的问题不是:
真正的问题是:
在这里,答案是肯定的。
校验器声称要保护:
- 元数据服务
- 环回地址
- RFC1918 私有范围
- IPv6 本地范围
但是主机名解析的差距让这些相同的目的地通过另一种表示形式重新进入了访问范围。
这正是那种值得获得 CVE 的漏洞。
## 修复分析
这个修复很稳健,因为它纠正了真正的边界,而不仅仅是一个症状。
在修补后的实现中,`validateHttpReqUrl()` 被修改为在批准请求之前解析主机名。
校验器现在:
- 从 `node:dns/promises` 导入 `lookup`
- 将校验视为异步操作
- 在允许非字面量主机名之前对其进行解析
- 解析每个解析到的地址
- 针对相同的阻止范围校验每个解析到的地址
这是正确的修复,因为它将信任决策从:
- “主机名文本看起来没问题吗?”
改变为:
- “实际目的地是否解析到了被允许的地址?”
这是从一开始就应该存在的安全属性。
该修复还增加了针对以下情况的回归测试覆盖:
- 解析为环回地址的主机名
- 解析为 RFC1918 空间的主机名
- 面向自托管者的允许列表内部主机
- 保留对元数据和环回目标不可绕过的防护
这就是你在真正的 SSRF 修复中想要的加固:
- 边界得到了纠正
- 预期行为在代码中进行了记录
- 并且测试锁定了该类别
该问题已在 **Typebot 3.16.0** 中得到解决。
## 披露
该问题是通过 GitHub 的安全报告流程私下报告的。
报告包括:
- `validateHttpReqUrl()` 中的根本原因
- `executeHttpRequest()` 中的下游执行路径
- 使用主机名解析到环回/私有目标的实际重现策略
- 以及一个用于隔离校验差距的独立演示
维护者接受了该问题,修复了校验逻辑,随后该漏洞被发布为:
**CVE-2026-34207**
修复程序已在 **Typebot 3.16.0** 中发布。
## 这个漏洞实际上教会了我们什么
这里的关键教训很简单:
这听起来很明显。
但是很多 SSRF 防护正是由于没有遵循这条规则而失效。
- 主机名字符串看起来是安全的。
- DNS 改变了它的含义。
- 请求依然发送出去了。
这就足够了。
这个漏洞还强化了关于一般 SSRF 审查的一些重要事项:
- 字面量 IP 过滤是不够的
- 主机名黑名单是不够的
- 编码 IP 检查是不够的
如果你不解析并校验真实的最终目的地,你的 SSRF 过滤器就仍然是不完整的。
这才是真正的结论。
## 关键点
- 当校验发生在目的地解析之前时,SSRF 防御就会失效
- 主机名文本校验与目的地 IP 校验不是一回事
- Webhook / HTTP Request 功能是真正的安全边界
- 当经过认证的 SSRF 能够到达元数据和内部服务时,它仍然可以是高危漏洞
- 一份有力的报告能将根本原因与影响联系起来,而不是仅仅提及其中一个
- 这个修复是正确的,因为它将决策转移到了实际解析后的目的地
## 结语
这个漏洞不在于花哨的 payload。
它在于提出了正确的边界问题。
在 Typebot 中,SSRF 校验器首先检查了主机名文本。
实际目的地随后由 DNS 决定。
网络客户端遵循了解析到的地址。
这个差距就是漏洞。
这就是为什么它成为了 **CVE-2026-34207**。
已在 **Typebot 3.16.0** 中修复。标签:CISA项目, MITM代理, SSRF, Web安全, 漏洞分析, 蓝队分析, 路径探测