In any corporate chat application, the "Private" tag carries an expectation of absolute trust. But recently, while poking around a major HR & IT management platform (on a private Bugcrowd program), I discovered that this trust was just an illusion.
I found a way for an Administrator to not only read private chats they weren't invited to, but to forcibly inject themselves into the conversation without consent.
I started my testing with two accounts:
- Employee Account: A standard user, member of a private chat.
- Admin Account: A privileged user, but NOT a member of that private chat.
First Bug: $$
My first goal was simple: Can the Admin see the chat metadata?
I logged in as the Employee and opened a private conversation. Burp Suite captured the traffic, and I spotted a request to:
POST /api/chat/twirp/chat.v1.ChannelService/GetChannel
The body was simple JSON:
{
"channel_id": "696f782d907541b1e786bcc1",
"include_details": true
}This request returned everything: the chat name, the members, and the details for a conversation not just the metadata.
I grabbed the Authorization token and the Role header from my Admin account. I then took the Employee's request to GetChannel (requesting the private chat ID) and swapped the headers to use the Admin's credentials.
The Result: 200 OK.
The server checked if I was an Admin, but it failed to check if I was a participant in that specific channel. I could now retrieve details for a conversation I had no business seeing.
This was a classic IDOR (Insecure Direct Object Reference), but I wanted to see how deep the rabbit hole went.
Bug Two : The Takeover (The "Duplicate" Deep Dive)
Reading details for a conversation is bad, but participation is worse. I noticed another endpoint being called when a user modified group settings:
POST /api/chat/twirp/chat.v1.ChannelService/EvaluateChannelGroupsUpdatePolicy
This endpoint handles logic like adding or removing members. I realized that if the access control logic was broken on GetChannel, it was likely broken here too.
The Setup
- I created a private chat between the Employee and another teammate.
- I initiated a request to add a member, capturing the request in Burp.
- I dropped the request to ensure it didn't process yet.
The Payload
The request body contained a parameter called include_role_ids. This array dictates who is allowed in the channel.
{
"channel_groups_update": {
"channel_id": "698b50a9852f2d5355c1dd2c",
"admin_group_update": {
"proposed": {
"include_role_ids": [
"EXISTING_MEMBER_ID",
"MY_ADMIN_ID" <--- I added this
]
}
}
}
}I repeated the header-swapping technique: I took this request (generated by the Employee) and signed it with the Admin's Authorization and Role headers.
The Result: 200 OK.
I went back to the Admin dashboard and refreshed. Suddenly, the private chat appeared in my list. I had successfully forced my way into the room.
The Impact
This vulnerability went beyond simple data leakage. By exploiting this, a malicious Administrator could:
- Read History: Access all past messages in the private channel.
- Spy: Monitor real-time messages between employees.
- Sabotage: Send messages posing as a member, kick other members out, or archive the chat to silence it.
I reported the vulnerability immediately.
- The Reward: The team validated the finding and awarded a $$ bounty.
- The Duplicate: Interestingly, I submitted the "Takeover" (Phase 2) as a separate issue. It was marked as a Duplicate of the first finding.
Tip : Just because a user is an "Admin" generally, doesn't mean they are an "Admin" of every object. Always test if horizontal or vertical privilege escalation allows access to private user data.
Happy Hacking! 🕵️♂️💻

