BookStack is a widely deployed open-source PHP-based documentation platform organizing content in a book/chapter/page hierarchy, used by SMEs and enterprises as an internal knowledge base and IT documentation system. Its security profile has several well-known characteristics: BookStack ships with default credentials documented in the official installation guide — admin@admin.com / password — which are frequently unchanged in self-hosted deployments particularly when installed from one-click deployment scripts; BookStack's image upload pipeline processes images with PHP's GD library and optionally ImageMagick — ImageMagick-based deployments are historically vulnerable to SSRF via MVG format coercions (ImageTragick class vulnerabilities) when policy.xml restrictions are not configured; BookStack provides a REST API authenticated with per-user API tokens created in user account settings — admin API tokens allow creating, reading, and deleting all books, chapters, and pages in the instance; the page content export feature generates PDFs using wkhtmltopdf or Puppeteer which fetch all referenced resources including external URLs in page content — attacker-controlled image URLs or CSS imports in page content can trigger SSRF through the export engine; and BookStack's Wysiwyg editor uses TinyMCE with server-side HTML sanitization — historical versions had insufficient sanitization allowing stored XSS through code block or raw HTML insertion. This guide covers systematic BookStack security assessment.
# BookStack default credentials: admin@admin.com / password
# Documented in official install guide — commonly unchanged in quick installs
BOOKSTACK_URL="https://docs.example.com"
# Test default credentials against the BookStack login endpoint
RESPONSE=$(curl -s -c /tmp/bs_cookies.txt \
"${BOOKSTACK_URL}/login" 2>/dev/null)
CSRF_TOKEN=$(echo "$RESPONSE" | grep -oP 'name="_token" value="\K[^"]+' | head -1)
LOGIN_RESPONSE=$(curl -s -X POST "${BOOKSTACK_URL}/login" \
-c /tmp/bs_cookies.txt -b /tmp/bs_cookies.txt \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "_token=${CSRF_TOKEN}&email=admin%40admin.com&password=password" \
-w "\n%{http_code}" 2>/dev/null)
CODE=$(echo "$LOGIN_RESPONSE" | tail -1)
if [ "$CODE" == "302" ] || [ "$CODE" == "200" ]; then
# Check if we landed on the dashboard (not back to login)
DASHBOARD=$(curl -s "${BOOKSTACK_URL}/" \
-b /tmp/bs_cookies.txt 2>/dev/null | grep -c "Your Recent Pages\|Your Recent Books")
if [ "$DASHBOARD" -gt 0 ]; then
echo "DEFAULT CREDENTIALS WORK: admin@admin.com/password — full admin access"
fi
fi
# BookStack REST API — authenticated with token_id:token_secret
# API tokens are created in user Settings → API Tokens
# An admin's API token gives full read/write access to all content
BOOKSTACK_URL="https://docs.example.com"
TOKEN_ID="your-token-id"
TOKEN_SECRET="your-token-secret"
# List all books (demonstrates read scope of token)
curl -s "${BOOKSTACK_URL}/api/books" \
-H "Authorization: Token ${TOKEN_ID}:${TOKEN_SECRET}" \
2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
books = d.get('data',[])
print(f'Books accessible ({d.get(\"total\",0)} total):')
for b in books[:10]:
print(f\" [{b['id']}] {b['name']} — {b.get('description','no desc')[:50]}\")
" 2>/dev/null
# List all pages — may include sensitive internal documentation
curl -s "${BOOKSTACK_URL}/api/pages?count=100" \
-H "Authorization: Token ${TOKEN_ID}:${TOKEN_SECRET}" \
2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
pages = d.get('data',[])
print(f'Pages: {len(pages)} / {d.get(\"total\",0)}')
for p in pages[:10]:
print(f\" [{p['id']}] {p['name']} (book_id={p.get('book_id','?')})\")
" 2>/dev/null
# Read specific page content (may contain passwords, API keys, internal URLs)
PAGE_ID=1
curl -s "${BOOKSTACK_URL}/api/pages/${PAGE_ID}" \
-H "Authorization: Token ${TOKEN_ID}:${TOKEN_SECRET}" \
2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
print(f'Page: {d.get(\"name\",\"?\")}')
print(f'HTML content (first 200 chars): {d.get(\"html\",\"\")[:200]}')
" 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| Default admin@admin.com/password credentials unchanged | POST /login with default credentials — full admin access to all documentation including sensitive content | Critical |
| REST API token with admin scope | GET /api/pages with admin token — reads all pages including content with embedded credentials and internal URLs | Critical |
| ImageMagick SSRF via MVG/MSL file upload | Upload image with MVG header containing url: delegate — triggers server-side HTTP request to attacker URL | High |
| PDF export SSRF via page content URLs | Create page with external resource URL — export to PDF triggers wkhtmltopdf fetch of attacker-controlled URL | High |
| Stored XSS via raw HTML in page content | Insert script tag via HTML editor or API page creation — executes in context of all users who view the page | High |
| Unauthenticated page access on public instances | GET /books or /pages without login — public BookStack instances expose all documentation without authentication | High |
Ironimo tests BookStack deployments for default admin@admin.com/password credentials unchanged from installation defaults, REST API token access providing full book and page content enumeration including sensitive documentation, ImageMagick MVG/MSL SSRF via the image upload endpoint on deployments using ImageMagick without policy.xml restrictions, PDF export pipeline SSRF via page content URLs fetched by wkhtmltopdf, stored XSS via raw HTML insertion in page content, and unauthenticated access to all documentation on publicly accessible BookStack instances.
Start free scan