I reported a security bug to Slack. The response was: working as intended
I find the conclusion disappointing. IMO, think twice before adding collaborators on a Slack app intended for supporting local development. They may be able to read your DMs.
Scenario: Alice (dev), Bob (manager), and Eve (dev and eavesdropper) are all part of the same Slack team
Alice and Eve are collaborators on a Slack App installed to their Slack workspace for the purpose of local development. With the right app configuration, Eve can now use her own access token to evesdrop on DMs between Alice and Bob.
- Create OAuth app with
message.im
event subscription permissions (and user scopesim:history, im:read
), socket mode enabled, and a localhost redirect URL. Install it to your Slack workspace. - Alice and Eve OAuth through the shareable URL granting access to the app. Bob does NOT need to OAuth.
- In fairness: this does require Alice to assert that the app will have access to her private messages.
- In this scenario, Alice assumes she's granting access to the localhost app that Slack is about to redirect to once authorization is granted.
- Eve needs only her own OAuth user token from the Oauth flow from this app. Trivial if Eve is app owner or collaborator:
# Make the POST request to exchange the code for an access token
curl -X POST https://slack.com/api/oauth.v2.access \
-d client_id="$SLACK_CLIENT_ID" \
-d client_secret="$SLACK_CLIENT_SECRET" \
-d code="$REDIRECT_CODE" \
-d redirect_uri="$SLACK_REDIRECT_URI"
# Extract .authed_user.access_token
- Eve now needs to subscribe to message events using her own OAuth user token. To repro this locally, she needs the App Token. This isn't necessary if Eve runs this repro on an internet-accessible endpoint. Sample implementation:
const { App } = require('@slack/bolt');
const app = new App({
token: process.env.SLACK_USER_TOKEN,
socketMode: true,
appToken: process.env.SLACK_APP_TOKEN,
});
app.message(async ({ message, say }) => {
console.log(`Message event in ${message.channel_type} ${message.channel}: ${message?.text}`);
});
(async () => {
await app.start(3000);
console.log('⚡️ Waiting for messages!');
})();
Eve (using her own access token) can now evesdrop on private messages between Alice and Bob.
Limitations
- Orgs can prevent certiain apps/permissions from being installed in the first place.
- The message only goes to one listener (anecdotally seems random), so if there are multiple listeners, each listener will only receive some of the messages.
- This does not allow reading DM history, only real-time DM events.
This is a fairly limited scenario (it requires being collaborators on an app for local development purposes or some other scenario where Eve can get her own access token for a Slack app that Alice has also authorized).
The report was defended as working as expected primarily because it requires Alice to explicitly consent to the app being able to read her messages.
But in this scenario, the app is for local development purposes. It redirects to localhost, so
- Alice's authorization code and access token are NEVER exposed to Eve.
- Yet Eve, using her own token, is able to access Alice's DMs.
- (and vice-versa)
So think twice before adding collaborators on a Slack app intended for supporting local development. I thought sharing the dev Slack app was just a way to speed up dev onboarding for a project (and avoid installing one app per dev to our Slack workspace). Then I found myself unintentionally observing private DMs between other teammates.
Discovered with this tool which this repro is based on.