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:
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