Mattermost Security Testing: Admin API, Bot Token, Webhook Credentials, and User Data Access

Mattermost is the dominant self-hosted team chat platform used by security-conscious organizations as a private alternative to Slack. It often becomes a high-value target because it aggregates sensitive internal communications, shared credentials, deployment tokens, and operational information. Key assessment areas: Mattermost admin API tokens provide complete access to all users, channels, messages, and direct messages across the entire deployment; bot tokens created for CI/CD and monitoring integrations are frequently stored in Docker Compose files, environment variables, and configuration management systems; Mattermost incoming webhook URLs allow posting as any configured integration without additional authentication; config.json stores SMTP, LDAP bind credentials, and database connection strings in plaintext; and the Mattermost bulk export feature exports all historical message content including direct messages. This guide covers systematic Mattermost security assessment.

Table of Contents

  1. Admin API and User Enumeration
  2. Message and Direct Message Access
  3. Configuration Credential Extraction
  4. Mattermost Security Hardening

Admin API and User Enumeration

# Mattermost REST API — admin token enumeration and access
MM_URL="https://mattermost.example.com"
TOKEN="your-mattermost-token"  # admin token from System Console or API

# Verify token and get current user info
curl -s "${MM_URL}/api/v4/users/me" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User: {d.get(\"username\")} email={d.get(\"email\")} roles={d.get(\"roles\")}')
print(f'System admin: {\"system_admin\" in d.get(\"roles\",\"\")}')
" 2>/dev/null

# Enumerate all users — full user directory including emails
for PAGE in 0 1 2 3 4; do
    curl -s "${MM_URL}/api/v4/users?page=${PAGE}&per_page=200" \
      -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
users=json.load(sys.stdin)
if not users: import sys; sys.exit(1)
for u in users:
    print(f'{u.get(\"username\")} email={u.get(\"email\")} roles={u.get(\"roles\")} active={u.get(\"delete_at\",1)==0}')
" 2>/dev/null || break
done

# List all teams
curl -s "${MM_URL}/api/v4/teams" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
teams=json.load(sys.stdin)
print(f'Teams: {len(teams)}')
for t in teams:
    print(f'  {t.get(\"name\")} ({t.get(\"display_name\")}) type={t.get(\"type\")} members={t.get(\"total_member_count\")}')
" 2>/dev/null

# List all channels (including private)
curl -s "${MM_URL}/api/v4/teams/{team_id}/channels?page=0&per_page=100" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
channels=json.load(sys.stdin)
for c in channels:
    print(f'  #{c.get(\"name\")} type={c.get(\"type\")} members={c.get(\"total_msg_count\")} messages')
" 2>/dev/null

Message and Direct Message Access

# Mattermost message access — channels and direct messages
MM_URL="https://mattermost.example.com"
TOKEN="your-mattermost-token"

# Get messages from a specific channel
CHANNEL_ID="channel-id-here"
curl -s "${MM_URL}/api/v4/channels/${CHANNEL_ID}/posts?page=0&per_page=60" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
posts = d.get('posts',{})
order = d.get('order',[])
print(f'Messages in channel: {len(order)}')
for post_id in order[:10]:
    p = posts.get(post_id,{})
    print(f'  [{p.get(\"create_at\")}] {p.get(\"user_id\")}: {p.get(\"message\",\"\")[:100]}')
" 2>/dev/null

# Search all messages across all channels — find credentials in chat
curl -s -X POST "${MM_URL}/api/v4/posts/search" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"terms":"password OR token OR secret OR api_key","is_or_search":true}' \
  2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
posts = d.get('posts',{})
print(f'Messages matching credential keywords: {len(posts)}')
for pid, p in list(posts.items())[:10]:
    print(f'  {p.get(\"user_id\")}: {p.get(\"message\",\"\")[:150]}')
" 2>/dev/null

# Get direct message channels — list all DM conversations
curl -s "${MM_URL}/api/v4/users/{target-user-id}/channels" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
channels=json.load(sys.stdin)
dms = [c for c in channels if c.get('type') == 'D']
print(f'Direct message channels for user: {len(dms)}')
for dm in dms:
    print(f'  DM channel: {dm.get(\"name\")} last_post={dm.get(\"last_post_at\")}')
" 2>/dev/null

Configuration Credential Extraction

# Mattermost config.json — all integration credentials in plaintext
# Located at /mattermost/config/config.json (Docker) or /opt/mattermost/config/config.json

python3 -c "
import json
with open('/mattermost/config/config.json') as f:
    config = json.load(f)

# Database credentials
sql_settings = config.get('SqlSettings',{})
dsn = sql_settings.get('DataSource','')
print(f'Database DSN: {dsn}')
# Example: postgres://mattermost:PASSWORD@localhost:5432/mattermost

# Email/SMTP credentials
email_settings = config.get('EmailSettings',{})
print(f'SMTP server: {email_settings.get(\"SMTPServer\")}:{email_settings.get(\"SMTPPort\")}')
print(f'SMTP user: {email_settings.get(\"SMTPUsername\")}')
print(f'SMTP password: {email_settings.get(\"SMTPPassword\")}')

# LDAP credentials
ldap_settings = config.get('LdapSettings',{})
print(f'LDAP server: {ldap_settings.get(\"LdapServer\")}')
print(f'Bind DN: {ldap_settings.get(\"BindUsername\")}')
print(f'Bind password: {ldap_settings.get(\"BindPassword\")}')

# Webhook security
service_settings = config.get('ServiceSettings',{})
print(f'Siteurl: {service_settings.get(\"SiteURL\")}')
print(f'Enable webhooks: {service_settings.get(\"EnableIncomingWebhooks\")}')
print(f'Enable webhook override username: {service_settings.get(\"EnablePostUsernameOverride\")}')
" 2>/dev/null

Mattermost Security Hardening

Mattermost Security Hardening Checklist:
Security TestMethodRisk
Admin token user enumerationGET /api/v4/users?page=0&per_page=200 — returns all user accounts with email addresses, roles, and activity status; enables targeted phishing and credential stuffing against corporate email accountsHigh
Message search for credential exposurePOST /api/v4/posts/search with terms "password OR token OR secret" — searches all channel messages for credential keywords; captures API keys, passwords, and secrets shared in chat conversationsCritical
config.json LDAP/SMTP credential extractionRead /mattermost/config/config.json — LdapSettings.BindPassword, EmailSettings.SMTPPassword, SqlSettings.DataSource with database credentials all stored in plaintextCritical
Incoming webhook username spoofingPOST to webhook URL with username override — if EnablePostUsernameOverride is true, messages can appear from any username; enables impersonating admins, security tools, or CI/CD systems in internal chatHigh
Bot token extraction from Docker Compose / env varsgrep -r "MATTERMOST\|MM_TOKEN" docker-compose.yml .env kubernetes/ — bot tokens stored as environment variables accessible to developers; enable admin API access to all channels the bot is a member ofHigh

Automate Mattermost Security Testing

Ironimo tests Mattermost deployments for admin token privilege assessment and user enumeration, message search for exposed credentials across all channels, config.json credential extraction including LDAP bind passwords and SMTP credentials, incoming webhook username override spoofing capability, bot token exposure in container environment variables, channel and DM access control boundary testing, and bulk export access control audit.

Start free scan