Bypassing Global Mention Restrictions in Slack

I think most people know the online chat platform “Slack” nowadays. It is mainly aimed at organizations, but was adopted in the last years also by a fair share of programming and other tech communities, such as Gophers (Go Programming Language) and NetSecFocus (NetSec-orientated Slack).

In July 2016 I started to investigate on how Slack implemented the permission checks in their web-client and analyze if any limitations are client-site only. I’ve found a global variable called TS exists which appears to hold the entire state for a Slack instance. Digging deeper, there was a object TS.model.team.prefs with several interesting keys:

{
  "...": "...",
  "who_can_at_everyone": "admin",
  "who_can_at_channel": "admin",
  "...": "...",
}

To give some background, Slack is organized in channels. Channels are similar to rooms in IRC, they have a topic, a description, members can join/leave… Users can be mentioned using @[USERNAME] which leads to a notification either on their mobile phone or in the browser, depending on their configuration. Additionally, there are some magic keywords such as @channel and @everyone, which pings everyone in the channel or the entire Slack. Those special mentions can be restricted in the team configuration to be only usable by admins or team owners.

If a member attempted to use the global mention while it was restricted, the following popup was shown:

Slack: Restricted Everyone Mention

When modifying the global mention configuration to allow all members to use the keywords, the object values will be instead of admin then regular. After changing them back to admin-only usage, I’ve attempted to by-pass the limitation. Opening the browser console, I’ve overwritten the values manually in the global configuration of the Slack web-client:

TS.model.team.prefs.who_can_at_everyone="regular";
TS.model.team.prefs.who_can_at_channel="regular";

After executing the two commands mentioned above in the browser console, I was able as non-privileged user to use restricted mentions. This shows that the check whether a user should be able to use global mentions is only enforced client-site, with no effective server-side filtering in place. This issue was reported on 2016/08/01 to Slack as part of their Bug Bounty on HackerOne. Although the issue is not critical, this could be abused in public Slack instances to spam effectively a majority of their user-base. The report was marked as duplicate, since another user found the bug a few days before using another vector.

After several requests for updates from the other user and myself, on 2017/08/25 we received feedback from Slack that the issue won’t be fixed, based on the fact that Slack considers teams as trusted space. This assumption is reasonable for companies using Slack, but is an issue for community-based Slack teams as such mentioned above. Gophers has at the time of writing 20.440 members and a channel called #general which cannot be left. NetSecFocus has about 6.000 members, elixir-lang at 16.761. All Slacks mentioned have web forms where a user can request an invite to said teams, without manual approval. Additionally, it was mentioned that it cannot be changed further, because it would break the functionality of our backend message servers.

In September 2017 the exploit stopped working. The key TS.model.team.prefs didn’t exist anymore. You also didn’t see a warning message as the one shown above, instead your @channel was just treated as normal text. After checking the global TS object again, there is now a new object called TS.permissions.members which contains callbacks such as:

{
  "...": "...",
  "canArchiveChannels": function t() {...},
  "canAtChannelOrGroup": function e() {...},
  "canAtMentionEveryone": function e() {...},
  "...": "...",
}

Instead of overwriting the string values as in the first case, one now overwrites the callback itself and the exploit works in its original fashion again:

TS.permissions.members.canAtChannelOrGroup = function e() { return true;}

Demonstration of the PoC:

I’ve seen this bug being abused publicly 1-2 times already and decided to publish it because of that, considering that Slack will not fix it. I understand the stance of Slack to say that this issue won’t affect the majority of their paying customers and they want to focus on other features, but I fear that this could cause trouble for public slack instances which could feel left alone, since there is no effective mitigation. Since the report there were 14 months for a implementation of server-side filtering.

Timeline:

  • 2016/08/01: Reported via HackerOne
  • 2016/08/01: Issue marked as duplicate
  • 2017/06/13: Slack reported issue would be fixed
  • 2017/06/14: Reported of non-duplicate responded that it’s still reproducible
  • 2017/08/25: Slack responded with wontfix due to architectural reasons and teams considered a trusted space
  • 2017/08/25: Requested public disclosure
  • 2017/08/25: Slack said they will discuss this internally and get back to me
  • 2017/09/17: Request for update
  • 2017/10/07: Public Disclosure