WEB SECURITY

Click2Shell

One Admin Click Runs PHP on Any WordPress Before 7.1.1. A parser differential in WordPress Core lets a crafted theme-preview URL turn a logged-in administrator's browser into a remote code execution primitive.

Matt Lucas  |  September 21, 2026  |  5 min
Editorial hero illustration
7.1.1
Patched WordPress version
1 click
Admin interaction required
0
Accounts the attacker needs
Aug 22, 2026
Reported to WordPress
TL;DR
  • What: A parser differential in WordPress Core theme-preview handling, named Click2Shell, lets a crafted URL drive the admin dashboard's own JavaScript into installing attacker-controlled PHP.
  • Impact: One click by a logged-in administrator writes a webshell into wp-content and executes it as the web server user, with wp-config.php database credentials in reach.
  • Fix / mitigation: Update WordPress Core to 7.1.1 and set DISALLOW_FILE_MODS in wp-config.php to disable theme and plugin installation through the web interface.
  • Who's at risk: Every site running WordPress Core 7.1.0 or earlier, with the sharpest exposure on agency and managed-hosting fleets where one browser holds live admin sessions for many sites.

A logged-in WordPress administrator clicking one link is enough to execute attacker-supplied PHP on the server. The bug, called Click2Shell by Paulos Yibelo of pwn.ai, who reported it, affects WordPress Core 7.1.0 and everything before it. WordPress fixed it in 7.1.1, released in September 2026. Complete technical detail and a proof of concept are already public, which means the gap between disclosure and mass scanning is measured in days, not weeks.

One value, two parsers

The root cause is a parser differential. A value carried in a WordPress theme-preview URL is interpreted once by the WordPress.org Themes API and a second time, by different and buggy logic, by JavaScript running in the administrator's browser. The two readings disagree. The server side treats the value as an ordinary theme identifier. The client side, handling the same string through jQuery selectors, resolves it into markup and behavior the server never intended.

That disagreement is the whole exploit. An attacker does not need an account, a token, or a foothold on the host. They need an administrator session to exist in a browser and that browser to load a URL of the attacker's choosing. Everything after that runs with the privileges WordPress already grants an administrator, and an administrator can install themes. A theme is PHP. Installing one is remote code execution with extra steps.

That also explains why WordPress's nonce system does not stop it. Nonces defend against a foreign page forging a request, because the foreign page cannot read the token. Here the request is assembled by WordPress's own admin JavaScript, inside the admin origin, with the nonce already present in the page. The token is supplied correctly. The forgery happens one layer up, in what the JavaScript was persuaded to build.

Why this is not a normal CSRF

Cross-site request forgery is usually rated a moderate problem because the damage ceiling is whatever the victim's role permits, and most roles permit very little. WordPress administrators are the exception. The administrator role owns file modification, and the theme and plugin installers write PHP into the webroot by design. Click2Shell chains a CSRF into that installer, so the outcome is not a changed setting or a deleted post. It is a webshell under wp-content, running as the PHP-FPM or Apache user, with database credentials sitting in wp-config.php one directory up.

The report reached WordPress on August 22, 2026, and the fix landed roughly two weeks later in 7.1.1. That is a fast turnaround by any measure, but the disclosure shipped with a working proof of concept, so every unpatched site now faces copy-and-paste exploitation rather than original research.

The precondition is a person, not a port

Click2Shell cannot be fired at a site from the outside with no human involved. It needs an administrator with a live session to open a crafted URL. Treat that as a weak defense. Administrator sessions survive for weeks in real deployments, agency staff manage dozens of sites from one browser profile, and a link in a support ticket or a contact form reply is all the delivery this requires.

Blast radius

What to run today

Two actions close this. Update core to 7.1.1, and set DISALLOW_FILE_MODS in wp-config.php so the theme and plugin installers are disabled entirely. The second control is the one worth keeping after the patch. Most production WordPress sites have no business installing code through the web interface, and turning that capability off removes an entire class of one-click escalation, not just this one.

wp core version --path=/var/www/html
wp core update --version=7.1.1 --path=/var/www/html
wp config set DISALLOW_FILE_MODS true --raw --path=/var/www/html
wp theme list --path=/var/www/html

If you manage a fleet, iterate the webroots rather than trusting an inventory. The version string lives in wp-includes/version.php as $wp_version, and grepping it across every vhost is faster and more accurate than reconciling a spreadsheet.

Assume some sites were already hit

The exploit leaves artifacts. A successful run installs a theme, which means a new directory under wp-content/themes holding PHP that no one on your team wrote, with a modification time after August 22, 2026. Check the options table for recently changed theme values, and check your web server access log for POST requests to /wp-admin/update.php carrying action=install-theme or upload-theme with an external Referer. Legitimate theme installs start from a click inside the admin dashboard, so the Referer is your own host.

A patch does not evict a shell

Updating to 7.1.1 stops the exploit, it does not remove code an attacker already wrote to disk. If you find an unexplained theme directory or a PHP file with a recent mtime in the webroot, rotate the database credentials in wp-config.php, rotate every administrator password and force session invalidation, then audit for new users and scheduled tasks created after August 22, 2026.

The pattern to watch for

Click2Shell is a parser differential, and that bug class is having a long run. The same shape shows up in request smuggling, in URL validation bypasses that become SSRF, and in every case where two components in a chain each implement their own reading of the same untrusted string. WordPress is fertile ground for it because the platform deliberately blurs the line between configuration and code. Any product where a privileged user can write executable files through a web form carries the same impact ceiling, and the only durable fix is to take that capability away in production.

Fix It Yourself

Everything above is what happened. This is what to run. The check commands change nothing.

Check whether you are exposed

Core version on a single install, tells you immediately if you are below 7.1.1

wp core version --path=/var/www/html

Core version for every WordPress webroot on the host, no inventory needed

find /var/www -path '*wp-includes/version.php' -exec grep -H '$wp_version =' {} \;

Whether file modification is already disabled, and which theme PHP files changed since the report date

grep -n 'DISALLOW_FILE_MODS' /var/www/html/wp-config.php; find /var/www/html/wp-content/themes -name '*.php' -newermt '2026-08-22' -print

Close it

Update WordPress Core to the patched 7.1.1 release

wp core update --version=7.1.1 --path=/var/www/html

Disable theme and plugin installation from the web interface, which blocks the exploit's final step

wp config set DISALLOW_FILE_MODS true --raw --path=/var/www/html

Quarantine a suspect theme directory instead of deleting it, replace SUSPECT_THEME with the directory name

mkdir -p /root/wp-quarantine && mv /var/www/html/wp-content/themes/SUSPECT_THEME /root/wp-quarantine/

Prompts for the agent you already run

Paste this when you need to know which sites in a fleet are exposed, before changing anything

Audit every WordPress install under /var/www for the Click2Shell flaw. For each webroot, print the path and the $wp_version value from wp-includes/version.php, then flag any install below 7.1.1. For each flagged install, also report whether DISALLOW_FILE_MODS appears in wp-config.php and list any file under wp-content/themes with an mtime after 2026-08-22. Show me every command you run and its raw output. Make no changes to any file, do not run wp core update, and stop with a summary table when the audit is done.

Paste this after you have reviewed the audit and decided to remediate

For the WordPress webroots I confirm below, run 'wp core update --version=7.1.1 --path=<webroot>' then 'wp config set DISALLOW_FILE_MODS true --raw --path=<webroot>', one site at a time. Before each site, show me the current version and a diff of the wp-config.php change you intend to make, and wait for my confirmation. If you find a theme directory that looks attacker-planted, do not delete it: move it to /root/wp-quarantine and stop so I can review it.

What to alert on

The single best observable is a theme install request whose Referer is not your own host. In the nginx access.log or Apache access_log, alert on POST requests to /wp-admin/update.php or /wp-admin/theme-install.php where the request body or query string contains action=install-theme or action=upload-theme and the Referer header points to any domain outside your site, since a legitimate install is always referred from within wp-admin. Pair that with process lineage from the endpoint agent or auditd execve records: a php-fpm or httpd parent spawning sh, bash, curl or wget is close to never normal on a WordPress host and is the signal that the installed PHP has run. For a file-level tripwire, watch inode creation events under wp-content/themes for new .php files, and correlate them against the WordPress options table keys stylesheet and template, which the exploit must change to activate the payload theme.

Questions about your exposure?

RedEye Security provides assessments for organizations that need to understand their real risk.

Talk to us