Wiki.js Security Testing: Default Credentials, GraphQL Introspection, SSRF, and File Upload

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.

Table of Contents

  1. Wiki.js Discovery and Setup Status
  2. GraphQL API Introspection and Mutations
  3. Authentication Provider Testing
  4. Storage Backend SSRF
  5. Wiki.js Security Hardening

Wiki.js Discovery and Setup Status

# 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

GraphQL API Introspection and Mutations

# 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

Wiki.js Security Hardening

Wiki.js Security Hardening Checklist:
Security TestMethodRisk
Setup wizard accessible before admin account creationGET /setup — attacker creates admin account on new Wiki.js installation with full platform controlCritical
GraphQL introspection revealing full schemaPOST /graphql with introspection query — reveals all types, mutations, and admin operationsMedium
User enumeration via GraphQL admin queryPOST /graphql with users.list query — authenticated users can list all wiki users and email addressesHigh
Storage backend SSRF via Git/S3 configurationConfigure storage backend with attacker-controlled Git remote or S3 endpoint — server connects to attacker's infrastructureHigh
File upload MIME type bypassUpload file with manipulated Content-Type or double extension — stored in wiki storage accessible to all usersMedium
LDAP authentication anonymous bind bypassConfigure LDAP with anonymous bind enabled — allows testing all usernames without valid bind credentialsHigh

Automate Wiki.js Security Testing

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