Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-34207 — The SSRF filter checked hostname text, but the actual destination was decided later by DNS. That gap let attacker-controlled Webhook URLs reach loopback, metadata, and private network targets. | Kitploit
Tools/GitHubGitHub/0xmrma/cve-2026-34207
Vulnerability AnalysisExploitationWeb SecurityPenetration TestingPapers & ResearchLearning & Education
GitHub0xmrma/cve-2026-34207

CVE-2026-34207

The SSRF filter checked hostname text, but the actual destination was decided later by DNS. That gap let attacker-controlled Webhook URLs reach loopback, metadata, and private network targets.

View Repository
63 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-34207

The SSRF filter checked hostname text, but the actual destination was decided later by DNS. That gap let attacker-controlled Webhook URLs reach loopback, metadata, and private network targets.

Intro

I found this issue while reviewing Typebot, an open-source chatbot builder, with a simple security question in mind:

What happens if SSRF protection validates hostname text, but the real destination is decided later by DNS?

In this case, that question led to a real bug.

Typebot's SSRF protection for Webhook / HTTP Request blocks validated only:

  • the URL string,
  • blocked hostname literals,
  • and literal IP formats

It did not resolve hostnames before allowing the request.

That meant a hostname like ssrf-repro.example could look harmless during validation, then resolve to:

  • 127.0.0.1
  • 169.254.169.254
  • or RFC1918/private network space

and still be fetched by the backend HTTP client.

That issue became CVE-2026-34207.

Typebot: Typebot on GitHub
CVE: CVE-2026-34207
Fixed in: 3.16.0

This affected Typebot, a widely used open-source chatbot platform. On its official site, Typebot is presented as trusted by 650+ companies worldwide. The site also advertises 2M+ monthly chats and 1.5M+ published bots.

photo0

Attack Chain

attacker-controlled Webhook URL -> hostname passes literal-only SSRF validation -> no DNS resolution before allow decision -> backend HTTP client resolves hostname to internal target -> server-side request reaches loopback / metadata / private network -> response data becomes available through execution logs


What Typebot Does

Typebot is a chatbot builder.

It lets users create flows that can:

  • ask questions
  • collect structured input
  • call external services
  • chain business logic
  • and trigger outbound HTTP requests through Webhook / HTTP Request blocks

That means outbound request execution is a real security boundary.

The important question here was not whether Typebot supported Webhook blocks.

The real question was:

Does the SSRF protection validate the actual destination the server will connect to, or only the hostname text that appears in the URL?

In this case, it only validated the text form first.

That was the mistake.


Why This Surface Was Worth Looking At

SSRF defenses fail in very predictable ways.

Most of the time, the interesting mistakes are not:

  • "you forgot to block 169.254.169.254"
  • or "you forgot to block localhost"

The stronger mistakes are boundary mistakes:

  • validation happens before canonicalization
  • validation happens before redirects
  • validation happens before DNS resolution
  • validation happens on one representation, but the network stack uses another

That was the right place to look here.

Typebot already had SSRF hardening logic for literal metadata IPs, loopback, private ranges, and encoded IP tricks.

That made the next question obvious:

What if the hostname is not literally dangerous, but resolves to a dangerous destination later?

That is exactly what happened.


Root Cause

The root issue was destination validation based on hostname text instead of resolved IP address.

In the vulnerable implementation, validateHttpReqUrl():

  • parsed the URL
  • allowed only http: and https:
  • blocked a short list of literal hostnames such as metadata.google.internal, metadata.goog, metadata, and localhost
  • detected literal decimal / hex / octal IP tricks
  • parsed literal IPv4 / IPv6 addresses
  • validated the address only if the hostname itself was already a literal IP

That is the important part.

If the hostname was a normal value like:

ssrf-repro.example

then parseIPAddress(hostname) returned null, and the validator stopped there.

No DNS resolution happened before approval.

So the vulnerable logic effectively reduced to:

const ip = parseIPAddress(hostname);
if (ip) {
  validateIPAddress(ip);
}

That means:

  • literal dangerous IPs were blocked
  • encoded dangerous IPs were blocked
  • but hostnames resolving to dangerous IPs were not

The second half of the bug was in the execution path.

In executeHttpRequest(), Typebot first ran validation and then later performed the actual request with ky(request.url, ...).

So the sequence was:

  • validate the URL string
  • accept the hostname
  • later resolve the hostname during the real outbound request
  • connect to the resolved internal target

That is the whole vulnerability.


Why This Is a Security Issue, Not Just Incomplete Filtering

The important distinction is where the trust decision was made.

A lot of bugs look small if you describe them badly.

If you describe this one as:

"the hostname filter was incomplete"

it sounds like a quality issue.

That is not the real problem.

The real problem was:

  • the server made a security decision before it knew the real destination
  • the network stack later connected somewhere else
  • and the application treated that request as valid

That is not cosmetic filtering weakness.

That is a trust-boundary failure.

And because the HTTP executor recorded response data in execution logs, the issue was not even blind in the strongest cases.

So this was not just:

  • "unexpected internal traffic happened"

It was:

  • internal traffic happened
  • and the attacker could often recover proof and response content through normal application behavior

That is a real SSRF vulnerability.


Proof of Concept

I used two proof layers because they demonstrated two different things.

PoC 1: self-contained local resolver demo

The first PoC isolated the root cause cleanly.

I used a small local harness that:

  • started a loopback HTTP server on 127.0.0.1
  • validated a URL such as http://ssrf-repro.example:18080/...
  • used a controlled resolver so ssrf-repro.example resolved to 127.0.0.1
  • then performed the request

That demonstrated the exact flaw:

  • validation passed because the hostname was not a literal blocked value
  • the later request still reached loopback

The captured output showed:

  • validator result: passed
  • request execution result: loopback reached
  • response body returned from the loopback service

That proved the validation gap directly.


PoC 2: real Typebot execution path

The second PoC showed the bug through the actual feature path that matters.

The simplest reproduction was:

  1. Map a benign hostname to loopback on the machine where the Typebot backend resolves DNS
  2. Start a local HTTP service
  3. Create a Webhook block pointing to that benign hostname
  4. Trigger the block through a normal authenticated preview or live execution path

A representative block looked like this:

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

With a hosts-file entry such as:

127.0.0.1 ssrf-repro.example

the validator still accepted the URL because:

  • ssrf-repro.example was not in blockedHostnames
  • it was not localhost
  • parseIPAddress("ssrf-repro.example") returned null
  • no destination IP validation occurred

Then the backend HTTP client resolved the hostname to 127.0.0.1 and connected anyway.

That established the full claim:

  • the vulnerable validation logic was reachable in real feature use
  • the request hit a blocked class of destination
  • and the application still treated it as a successful outbound HTTP request

Why The Two PoCs Were Chosen This Way

The first PoC proves root cause.

The second PoC proves product impact.

That split matters.

If you only show:

"this filter accepts a hostname"

you have not shown enough.

Download Tool