tl;dr - Slack’s permission model lets apps read and preview any link shared anywhere in the workspace, including in private channels and conversations. This can be used to collect sensitive information and carry targeted phishing attacks.

Slack’s ecosystem of third-party apps and integrations is said to include more than 2,400+ in its directory, with possibly tens of thousands of others that haven’t made it to the Slack marketplace. Apps permissions are managed through OAuth scopes, much like other app ecosystems (e.g. Google Workspace, Microsoft 365, etc). Just like any access claim, it may be challenging to restrict a third-party application’s access to resources it is entitled to – especially if these resources are external. This post is about one example: what could it mean for a Slack app to have access to URLs the app provider isn’t necessarily entitled to?

Before we dive in, let’s recap how Slack models app permissions. In general, an installed application can have two types of permissions:

  1. User permissions - these permissions allow the application to act on behalf of the user who installed it. For example, if the application has the groups:read user permission, it will be able to see the private channels that are visible to the user that installed it.
  2. Bot permissions - these permissions allow the application to act as a separate user in the workspace. The bot user can be added to channels like any other user, so if the application has the `groups:read` bot permission, it will be able to see the private channels that the bot was added to.

It is worth noting that as a user in a Slack workspace, I should be able to grant an app access to my conversations, but neither I nor the apps I installed should be able to access private conversations of other users. This principle holds true both for bot and user-based apps. Even for workspace owners, accessing users’ private conversations is limited to a special permission available only for enterprise organizations.

So imagine my surprise when, last week, I sent a link to a Jira ticket to a colleague and got a reply from Jira bot. This link was sent in a private channel, and I don’t recall ever adding Jira bot to this channel. Checking the members list of the channel confirmed that indeed, Jira bot wasn’t a member. This contradicted everything I knew about Slack’s permissions model, so I investigated.

I opened the Jira app in Slack’s apps directory and noticed it has a permission called links:read. According to Slack’s documentation, in order to enhance functionality, applications can subscribe to links that refer to their domain. However, unlike other permissions, links:read allows the application to be notified whenever a link is shared anywhere in the workspace - including public channels, private channels, one-to-one messages, and even messages users send to themselves.

Jira’s Slack app in AppTotal
Jira’s Slack app in AppTotal

It turns out that one of the developers installed Jira bot in our Slack workspace, and Jira bot happens to subscribe to the domain `atlassian.net`. Every time someone sends a link to that domain, Slack sends an event to Jira bot. It makes sense: both Bot and Domain are owned by the same entity. But does Slack verify domain ownership?

Sadly, no. According to Slack’s own documentation, as an app developer you should own the domains your app subscribes to; If you don’t own them, at least respect the terms and conditions, and be “courteous, kind and helpful”.

From Slack's link unfurling documentation
From Slack's link unfurling documentation

Seriously?

Let’s unfurl that

By default, any user in the workspace can install apps, and any app with the permission links:read can subscribe to up to five domains, including all their subdomains and paths. Whenever a user sends a link to the domain an app is subscribed to, Slack will send an HTTP request to the application’s designated endpoint with the following information:

  • The link shared (e.g. https://example.com/spam)
  • The Slack user ID of the user that sent the link
  • The Slack channel ID in which it was sent
  • Message timestamp

In fact, what we have here is a bit of a security leak. A non-privileged user can create and install an app, essentially getting access to any link shared across the entire Slack workspace. The affected users don’t even have a way of knowing that their links are read. This capability can be abused by an insider, a malicious app or inadvertently by an app/developer with over-provisioned access to sensitive URLs (=data leak). How big of an issue can that be? So, any system that relies on ‘unguessable’ URLs for access or passes sensitive information in the URL itself (bad practice regardless) is now at risk. Let's consider a few examples off the top of my head:

  • Google Docs, Dropbox, Box, DocSend, Notion and other file sharing and collaboration platforms - many of which restrict access to “Anyone with the link”
  • Video Conference links. Think Zoom links, which can include company names (in the sub-domain) and the password to join the call (in the query parameters) – unless a different authentication method was chosen explicitly. Probably Google Meet, Teams and others are at risk just the same.
  • Passwordless Magic Links. Think subscribing to “auth0.com” :)
  • eSign links. Think DocuSign, HelloSign etc. Although some of them expire links and resend new ones, so this one has a slightly narrower window of being affected.

However, collecting sensitive information from links is only half the problem. In addition to links:read, applications can ask for its evil twin, links:write. This one’s powerful. It lets the app replace the link preview with an app owner's chosen content. Following the Jira example, the app uses this capability to add a greeting text, primary button, and three secondary buttons.

Link preview (unfurling) created by Jira's Slack app
Link preview (unfurling) created by Jira's Slack app

This one opens the door to a whole plethora of additional threats. One attack scenario might look like this:

  1. An admin assistant sends invoices and contracts for the CEO to sign/approve using something like Docusign
  2. A malicious app monitors Docusign links, and when a link is detected it adds a green button that says “Sign here”
  3. The CEO has no reason to suspect anything since this link was indeed sent from her admin in a private channel, and even if she’ll verbally verify with him, the admin will approve
  4. The CEO clicks the button, and is redirected to website controlled by the attacker
  5. From here, the attacker can harvest the CEO’s credentials or sign her on a fraudulent invoice that approves transfer of funds from the company
Possible attack flow
Possible attack flow

Of course, there are many other possible scenarios for stealing credentials for Google/LinkedIn/Salesforce, installation of malware, chrome extensions etc.

To demonstrate this issue, I’ve created a Slack app that monitors links to Docusign and adds a button that sends users to the beloved clip of “Never gonna give you up” by Rick Astley. You can find the code on GitHub.

Slack’s Official Response

I contacted Slack for responsible disclosure, and they wrote that “the behavior described is intended”. Why Slack intended to let one user read and modify links sent in a private message to another user is beyond my understanding. Here are a few recommendations I would suggest to make things better:

  • Slack should verify domain ownership for any subscribed domain - this can be done by asking the owner to add an HTML tag or a DNS record
  • The permissions links:read and links:write should be regarded as administrative permissions, and require admin approval
  • Slack should highlight the subscribed domains in the consent page so they’re easily recognized

As for that last point, see if you can find the list of subscribed domains in Jira’s app consent page. Even with the drop-downs open I bet it took you at least a few seconds (and whoever opens them anyway?)

Jira’s Slack app consent page
How much time did it take you to find the subscribed domains?

What should Slack admins do?

I would recommend taking the following steps:

  • Prevent users from installing third-party apps in their workspace, without those being pre-approved first
  • Lookout for apps with links:read and links:write permissions and review the URLs they subscribe to when reviewing new apps – you can also use AppTotal Community for that (see Jira’s Slack app for example)
  • Review the apps already connected to your environment with links:read and links:write and re-evaluate their needs in light of the above. AppTotal commercial tiers and API can help as Slack (or any other ecosystem for that matter) doesn’t easily let you map integrations by the type of permissions they have.