Wiki.js is a popular open-source wiki platform built on Node.js with a modern Vue.js frontend, used by enterprises, development teams, and open-source projects as an internal knowledge base. Its security assessment covers several important attack surfaces: the first-launch setup wizard at /setup is accessible without authentication and requires immediate completion after deployment — any visitor who reaches it before the administrator can create the admin account; Wiki.js exposes a GraphQL API at /graphql that serves both the admin interface and external integrations — introspection reveals the complete schema including administrative mutations for user management, group permissions, and authentication providers; storage backend configuration in the admin panel supports Git, S3, Azure Blob, and local filesystem storage — configuring an attacker-controlled Git remote or S3 endpoint during a pentest demonstrates SSRF via storage backend sync; Wiki.js allows file attachments to wiki pages with upload via /u — the file type restrictions depend on the configured storage backend and wiki settings; and Wiki.js authentication supports multiple providers including local, LDAP, OAuth, and SAML — misconfigured LDAP authentication with anonymous bind allows user enumeration or credential bypass. This guide covers systematic Wiki.js security assessment.
# Wiki.js version fingerprint from the login page
curl -s "https://wiki.example.com/login" 2>/dev/null | \
grep -oE 'Wiki\.js [0-9]+\.[0-9]+\.[0-9]+' | head -1
# Check if setup wizard is accessible (should only be available before first admin setup)
curl -s -o /dev/null -w "%{http_code}" \
"https://wiki.example.com/setup" 2>/dev/null
# 200 = setup accessible — critical finding; attacker can create admin account
# Check the health endpoint
curl -s "https://wiki.example.com/healthz" 2>/dev/null
# Wiki.js GraphQL endpoint — powers both admin UI and external API consumers
WIKI_URL="https://wiki.example.com"
# Introspection query — reveals full schema without authentication (when not disabled)
curl -s -X POST "${WIKI_URL}/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name kind fields { name type { name kind } } } } }"}' \
2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
if 'data' in d and '__schema' in d['data']:
types = d['data']['__schema']['types']
print(f'Schema types: {len(types)}')
mutations = [t for t in types if t['name'] == 'Mutation']
if mutations and mutations[0].get('fields'):
print('Mutations:')
for f in mutations[0]['fields'][:15]:
print(f\" {f['name']}\")
elif 'errors' in d:
print(f'Introspection blocked: {d[\"errors\"][0].get(\"message\",\"?\")}')
" 2>/dev/null
# With authentication — test privileged mutations
JWT_TOKEN="your-wiki-jwt-token"
# List all users via admin GraphQL query
curl -s -X POST "${WIKI_URL}/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-d '{"query":"query { users { list { id name email isActive isVerified createdAt } } }"}' \
2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
users = d.get('data',{}).get('users',{}).get('list',[])
print(f'Users ({len(users)} total):')
for u in users[:5]:
print(f\" {u.get('email','?')} name={u.get('name','?')} active={u.get('isActive','?')}\")
" 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| Setup wizard accessible before admin account creation | GET /setup — attacker creates admin account on new Wiki.js installation with full platform control | Critical |
| GraphQL introspection revealing full schema | POST /graphql with introspection query — reveals all types, mutations, and admin operations | Medium |
| User enumeration via GraphQL admin query | POST /graphql with users.list query — authenticated users can list all wiki users and email addresses | High |
| Storage backend SSRF via Git/S3 configuration | Configure storage backend with attacker-controlled Git remote or S3 endpoint — server connects to attacker's infrastructure | High |
| File upload MIME type bypass | Upload file with manipulated Content-Type or double extension — stored in wiki storage accessible to all users | Medium |
| LDAP authentication anonymous bind bypass | Configure LDAP with anonymous bind enabled — allows testing all usernames without valid bind credentials | High |
Ironimo tests Wiki.js deployments for setup wizard accessibility before admin account creation, GraphQL introspection revealing the full schema including administrative mutations, user enumeration via authenticated GraphQL admin queries, storage backend configuration enabling SSRF to attacker-controlled Git remotes or S3 endpoints, file upload MIME type bypass allowing dangerous file types in wiki storage, and LDAP authentication misconfiguration enabling anonymous bind user enumeration.
Start free scan