Sploitus

Exploit for CVE-2026-34207

kitploit · 2026-08-31

Exploit Code

MARKDOWN545 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-0XMRMA-CVE-2026-34207
# CVE-2026-34207

SSRF过滤器检查了主机名字符串,但实际目的地是由后续的DNS决定的。这一漏洞使得攻击者控制的Webhook URL能够到达回环地址、元数据服务和私有网络目标。

## 介绍

我在审查**Typebot** (一个开源聊天机器人构建器)时发现了这个漏洞,心中带着一个简单的安全问题:

**如果SSRF保护验证的是主机名字符串,而实际目的地是由后续的DNS决定的,会发生什么?**

在这个案例中,这个问题引出了一个真正的漏洞。

Typebot 的 `Webhook` / HTTP 请求块的 SSRF 保护仅验证了:

  * URL 字符串,
  * 封禁了主机名字面量,
  * 以及字面量 IP 格式



它**没有** 在允许请求之前解析主机名。

这意味着像 `ssrf-repro.example` 这样的主机名在验证时看起来无害,但随后可能解析为:

  * `127.0.0.1`
  * `169.254.169.254`
  * 或 RFC1918/私有网络空间



并且仍然会被后端 HTTP 客户端获取。

这个问题成为了 **CVE-2026-34207** 。

**Typebot:** Typebot on GitHub  
**CVE:** CVE-2026-34207  
**修复版本:** `3.16.0`

这影响了**Typebot** ,一个被广泛使用的开源聊天机器人平台。在其官方网站上,Typebot 被宣传为受全球 **650+ 家公司** 信赖。该网站还宣称拥有 **200 万+ 月聊天量** 和 **150 万+ 已发布的机器人** 。

![photo0](https://assets.kitploit.com/production/public/readmes/14490/80f8b8a2729aa09d3084cb2df9b80d368ac8b08281a42f0e15ddf318fc247275.png)

* * *

## 攻击链

`攻击者控制的 Webhook URL -> 主机名通过仅验证字面量的 SSRF 检查 -> 在允许判定前未进行 DNS 解析 -> 后端 HTTP 客户端将主机名解析为内部目标 -> 服务端请求到达回环地址 / 元数据服务 / 私有网络 -> 响应数据通过执行日志变得可用`

* * *

## Typebot 的功能

**Typebot** 是一个聊天机器人构建器。

它允许用户创建可以执行以下操作的流程:

  * 提问
  * 收集结构化输入
  * 调用外部服务
  * 串联业务逻辑
  * 并通过 `Webhook` / HTTP 请求块触发出站 HTTP 请求



这意味着出站请求执行是一个真正的安全边界。

这里的关键问题不是 Typebot 是否支持 Webhook 块。

真正的问题是:

**SSRF 保护验证的是服务器将要连接的实际目的地,还是只是 URL 中出现的主机名字符串?**

在这个案例中,它首先只验证了文本形式。

这就是错误所在。

* * *

## 为什么这个攻击面值得关注

SSRF 防御以非常可预测的方式失效。

大多数时候,有趣的错误不是:

  * "你忘了封禁 `169.254.169.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 解析。

因此,脆弱的逻辑实际上简化为:

root@kitploit:~
    
    
    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. 通过正常的经过身份验证的预览或实时执行路径触发该块



一个代表性的块如下所示:

root@kitploit:~
    
    
    {
      "id": "blk-webhook",
      "type": "Webhook",
      "options": {
        "webhook": {
          "method": "GET",
          "url": "http://ssrf-repro.example:8000/"
        }
      }
    }
    

配合如下的 hosts 文件条目:

root@kitploit:~
    
    
    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 解析改变了该值的实际安全含义
  * 后端连接到了一个被禁止的目的地
  * 并且功能路径仍然完成



这是完整的故事。

* * *

## 严重性与分类

该问题被合理分类为**高** 。

公告分类为:

  * **CWE-918** : 服务器端请求伪造 (SSRF)
  * **CWE-20** : 输入验证不当
  * **CVSS:**



root@kitploit:~
    
    
    CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L
    

这说得通。

这本身不是一个未经身份验证的互联网范围的 SSRF。

所需的权限为**低** ,因为独立漏洞需要一个能够配置或触发 `Webhook` / HTTP 请求块的参与者。

但是在这个边界内,影响是严重的:

  * 回环访问
  * 私有网络访问
  * 元数据访问
  * 以及通过日志的数据泄露



这本身就是一个强 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 请求功能是真正的安全边界
  * 经过身份验证的 SSRF 在到达元数据和内部服务时仍然可以是高严重性
  * 一份强有力的报告要连接根本原因和影响,而不仅仅是其中之一
  * 修复是正确的,因为它将决策移到了实际解析的目的地



* * *

## 最后的话

这个漏洞并不在于什么花哨的 payload。

而在于提出正确的边界问题。

在 Typebot 中,SSRF 验证器首先检查了主机名字符串。 实际目的地是由后续的 DNS 决定的。 网络客户端遵循了解析后的地址。

这个缺口就是漏洞。

这就是为什么它成为了 **CVE-2026-34207** 。

已在 **Typebot 3.16.0** 中修复。