WordPress Attacks Using wp2shell: Why the Update Alone Isn’t Enough to Give the All-Clear

In short:

Since July 17, 2026, a critical vulnerability in the WordPress core has been actively exploited—without requiring a login or a plugin, on a default installation. Anyone who has updated since then is protected against new attacks. However, the version number does not indicate whether anyone gained access to the system between July 17 and the

Since July 17, 2026, a critical vulnerability in the WordPress core has been actively exploited—without requiring a login or a plugin, on a default installation. Anyone who has updated since then is protected against new attacks. However, the version number does not indicate whether anyone gained access to the system between July 17 and the update. It is precisely this window of a few hours or days that you should check now.

What Happened

wp2shell is not a single vulnerability, but a chain: a vulnerability in the REST API batch route and an SQL injection in the WordPress core interact in such a way that an anonymous attacker can gain access to the server and execute code. The full exploit chain affects WordPress versions 6.9.0 through 6.9.4, as well as 7.0.0 and 7.0.1. Versions 6.8.0 through 6.8.5 contain the SQL injection vulnerability but not the full standard exploit chain. The issue has been fixed in versions 6.8.6, 6.9.5, and 7.0.2.

Figures from Patchstack show just how quickly such vulnerabilities are exploited today: The first exploit attempts began about 90 minutes after the update was released, and by July 22, over 65,000 attempts had been blocked. The observed activities included database queries, the extraction of user data, the creation of new administrators, and the deployment of webshells.

The downside is that while an update fixes the vulnerability, it doesn’t undo anything that happened before. An additional administrator account or a PHP file in the uploads folder will remain exactly where it is after the update. And a clean scan from Wordfence, Imunify, or the WP Toolkit is a good sign, but it’s not forensic evidence.

The most important tip: User accounts

The most telling sign at this time is the presence of administrators that no one created. Accounts that stand out include those registered since July 17, 2026; usernames following the pattern wp2_* or w2s_* —such as wp2_65aff658915c; and email addresses on domains like @wp2shell.invalid or @wp2shell.shellcode.lol. Equally suspicious: an existing user who suddenly has administrator privileges, gaps in the sequence of user IDs, or orphaned entries in wp_usermeta whose user_id no longer exists at all in wp_users.

For a quick look, WP-CLI is all you need:

wp user list --role=administrator \
  --fields=ID,user_login,user_email,user_registered

The more thorough approach is to use the database, because it also reveals permissions that are not immediately apparent in the backend:

SELECT u.ID, u.user_login, u.user_email, u.user_registered, m.meta_value
FROM wp_users u
JOIN wp_usermeta m ON m.user_id = u.ID
WHERE m.meta_key LIKE '%capabilities%'
  AND m.meta_value LIKE '%administrator%'
ORDER BY u.user_registered DESC;

The key point is the reverse: Some exploit variants delete the administrator account they created after use. So the fact that no unauthorized administrator account appears does not prove that nothing happened.

Files, Plugins, and Webshells

The second point to check is files that have been added or modified since July 17. You should pay particular attention to wp-content/plugins/, wp-content/mu-plugins/, wp-content/uploads/, wp-content/cache/, and wp-content/upgrade/. PHP files in the “Uploads” directory or in the cache simply do not belong there.

find /var/www/vhosts/DOMAIN/httpdocs \
  -type f -name "*.php" \
  -newermt "2026-07-17 00:00:00" \
  -printf "%TY-%Tm-%Td %TH:%TM:%TS %u %g %p\n" | sort

When it comes to plugins, it’s worth paying attention to names that sound harmless, such as “Security,” “Cache,” or “Performance,” and especially to “must-use” plugins that don’t even appear in the standard plugin menu (wp plugin list --status=must-use). Wiz has observed both minimal one-liner webshells and extensive variants disguised as plugins—some of which intentionally return a completely normal 404 page if the correct secret POST argument is missing.

In the files themselves, there are eval($_POST[...]), base64_decode, gzinflate, shell_exec, passthru, system or exec the usual suspects. A few isolated instances aren’t proof enough; legitimate plugins also use some of these functions. The combination of obfuscation, eval, user input, and system commands, on the other hand, is pretty clear-cut.

Logs and Database

A characteristic feature of this wave is POST requests to /wp-json/batch/v1 or the versions with ?rest_route=/batch/v1. Since July 21, the parameter has in some cases been included only in the POST body—in the access log, it simply appears as POST / HTTP/1.1. A simple search for “batch/v1” in the URL is therefore insufficient.

zgrep -Eai \
'batch/v1|rest_route.?=.?/batch/v1|author_exclude|author__not_in|wp2shell|wp/v2/users|wp/v2/plugins|upload-plugin' \
/var/www/vhosts/system/DOMAIN/logs/*access* \
/var/log/apache2/* /var/log/nginx/* 2>/dev/null

At first glance, such hits merely indicate an attempt. What’s interesting is what happens next: a successful request to /wp-admin/ from an external IP address, a plugin upload via /wp-admin/update.php?action=upload-plugin shortly after the batch request, or the direct retrieval of a new PHP file at wp-content/uploads/. This exact sequence was observed in cases where takeovers were actually successful.

And because some publicly available exploits clean up after the test—that is, remove the user and the webshell—the database is often more informative than the log. Typically, what remains are unexpected customize_changeset entries, changesets with implausible post_parent values, suspicious oEmbed cache entries, unknown serialized data in wp_options, or a cron job that no one set up. You should also check for changes to active_plugins, siteurl, and home.

In the case of a complete server takeover, there are additional traces outside of WordPress: cron jobs under www-data, new files in /tmp, processes such as curl, wget or python, launched by PHP-FPM, outgoing connections from the web server to unknown addresses, unknown SSH keys, or a modified .htaccess, .user.ini or wp-config.php.

What Is Not Yet an Incident

The opposite is just as important; otherwise, every audit turns into an emergency. Blocked requests in Wordfence or Imunify, high traffic to /wp-json/batch/v1, requests from suspicious VPS addresses, a scanner user-agent, or a request ending with a 403 or 500 error: All of this simply means that the website was viewed. Even the fact that a site ran on a vulnerable version for a few days is not, in and of itself, cause for concern. The campaign consists largely of automated scanning via cloud and VPS providers, and most of these attempts come to nothing.

When It’s a Security Incident

You should treat a website as an incident as soon as at least one of the following applies: an unknown administrator or unexplained admin privileges, an unknown plugin or MU plugin, a new PHP file in the “uploads” or “cache” directories, successful admin access following a batch request, suspicious changeset or oEmbed artifacts, or a web server that executes system commands and establishes connections to external sources.

Then it’s no longer just a matter of cleaning up individual files, but of performing a thorough recovery, generating new login credentials and keys, and verifying that absolutely nothing has been left behind. Less dramatic, but just as useful, is the opposite scenario: a documented audit that shows nothing has gone wrong. For client projects, this is often more valuable than the update itself—since the automatic update has already taken care of that, at the very latest, but it doesn’t answer the question about what happened in the time leading up to it.

Wenn auch Sie mit einer Herausforderung konfrontiert sind und Unterstützung oder Beratung benötigen, dann kontaktieren Sie uns gerne.

Für allgemeine Anfragen / Offertanfragen / Projektfragen:

E-Mail: solutions@rettenmund.com

Zur Blog-Übersicht

More interesting new articles

WE WORK FOR, AMONG OTHERS ...

Stiftung für Konsumentenschutz

swiss alpine herbs - SWISSALPINEHERBS

Schweizerische Eidgenossenschaft

BETAX Genossenschaft BERN

männer.ch - Dachverband Schweizer Männer- & Väterorganisationen

UBS Bank - Y

medbase Gruppe

REKA Genossenschaft

Kyburz Saphire, Safnern - Optische Komponenten Saphir, Keramik

Edition Königstuhl

Gemeinde Urtenen-Schönbühl

LKBV - Luzerner Kantonal-Blasmusikverband

stadtwerke kongress aarau

Casafair - Eigentum mit Verantwortung

Green Golfreisen

Swisspower AG

Gemeinde Moosseedorf

Einwohnergemeinde Lengnau

reCIRCLE AG - Die Mehrwegsystem-Lösung

HESAV - Haute Ecole de Santé Vaud