<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rafael Cosquiere</title>
    <description>The latest articles on DEV Community by Rafael Cosquiere (@zft9xgy).</description>
    <link>https://dev.to/zft9xgy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1210371%2Fbcd2aa9d-cc58-49a3-9b51-f365c7dd7a97.png</url>
      <title>DEV Community: Rafael Cosquiere</title>
      <link>https://dev.to/zft9xgy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zft9xgy"/>
    <language>en</language>
    <item>
      <title>Forensic analysis of a wp2shell WordPress compromise: two operators and four persistence locations</title>
      <dc:creator>Rafael Cosquiere</dc:creator>
      <pubDate>Thu, 30 Jul 2026 06:45:52 +0000</pubDate>
      <link>https://dev.to/zft9xgy/forensic-analysis-of-a-wp2shell-wordpress-compromise-two-operators-and-four-persistence-locations-4cif</link>
      <guid>https://dev.to/zft9xgy/forensic-analysis-of-a-wp2shell-wordpress-compromise-two-operators-and-four-persistence-locations-4cif</guid>
      <description>&lt;p&gt;A few days ago, a WordPress site I manage was defaced with the message "Hacked by CoupDeGrace".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1eflsbg157mkfr5gxake.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1eflsbg157mkfr5gxake.png" alt="Hacked by CoupDeGrace - ganteng.html" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On my own site I wrote about the incident from a less technical and more personal angle: &lt;a href="https://rafaelcosquiere.com/en/notes/hacked-by-coupdegrace-wordpress-hacked-wp2shell/" rel="noopener noreferrer"&gt;Hacked by CoupDeGrace: forensic analysis of a WordPress site hacked with wp2shell&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article is the technical version. It focuses on the evidence, the attack sequence, the persistence mechanisms, the log analysis and the checks used to decide whether the environment was clean.&lt;/p&gt;

&lt;p&gt;The customer name, customer-owned domains, exact server paths and live access secrets have been removed. Malicious filenames, timestamps, counts and attacker infrastructure are kept when they are useful as indicators of compromise. Any token that could still provide access to a backdoor is redacted.&lt;/p&gt;

&lt;p&gt;All timestamps are CEST.&lt;/p&gt;

&lt;h2&gt;
  
  
  Executive summary
&lt;/h2&gt;

&lt;p&gt;The primary WordPress site was compromised on &lt;strong&gt;19 July 2026 at 07:53:57&lt;/strong&gt; through the unauthenticated &lt;code&gt;wp2shell&lt;/code&gt; remote code execution chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CVE-2026-63030&lt;/code&gt;: REST API batch-route confusion&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CVE-2026-60137&lt;/code&gt;: SQL injection through &lt;code&gt;author__not_in&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Target endpoint: &lt;code&gt;batch/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Vulnerable RCE-chain versions: WordPress &lt;code&gt;6.9.0&lt;/code&gt; to &lt;code&gt;6.9.4&lt;/code&gt;, and &lt;code&gt;7.0.0&lt;/code&gt; to &lt;code&gt;7.0.1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Patched RCE-chain versions: &lt;code&gt;6.9.5&lt;/code&gt; and &lt;code&gt;7.0.2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WordPress &lt;code&gt;6.8.x&lt;/code&gt; was affected by the SQL injection issue, but not by the complete batch-route RCE chain. The fix for that branch was released in &lt;code&gt;6.8.6&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This was not a single intrusion followed by a defacement. The site had been colonised by at least two separate operator clusters. They used different persistence systems, deployed different payloads and removed each other's administrator accounts and files.&lt;/p&gt;

&lt;p&gt;The compromise was still active on &lt;strong&gt;29 July at 13:14&lt;/strong&gt;, eight minutes after the WordPress core had been updated to &lt;code&gt;7.0.2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Updating closed the vulnerable entry point. It did not remove the attackers.&lt;/p&gt;

&lt;p&gt;Confirmed impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote command execution as the web-server user&lt;/li&gt;
&lt;li&gt;Exfiltration of email addresses from WordPress and WooCommerce tables&lt;/li&gt;
&lt;li&gt;Database credentials exposed in plain text inside access logs&lt;/li&gt;
&lt;li&gt;Deployment of a bulk-mail module with several delivery fallbacks&lt;/li&gt;
&lt;li&gt;Use of the host as a spam relay&lt;/li&gt;
&lt;li&gt;Persistence in the system user's crontab&lt;/li&gt;
&lt;li&gt;Persistence in &lt;code&gt;wp_options&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Persistence in &lt;code&gt;/tmp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creation of a backdoor administrator&lt;/li&gt;
&lt;li&gt;Removal of other WordPress sessions&lt;/li&gt;
&lt;li&gt;Wordfence disabled by renaming its directories&lt;/li&gt;
&lt;li&gt;Attempts to locate and write into other virtual hosts on the same server&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scope and limitations
&lt;/h2&gt;

&lt;p&gt;The material available for the analysis was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A full copy of the primary site's &lt;code&gt;public_html&lt;/code&gt;, around 1.9 GB&lt;/li&gt;
&lt;li&gt;Access and error logs for five customer-owned domains on the same hosting environment&lt;/li&gt;
&lt;li&gt;No database dump&lt;/li&gt;
&lt;li&gt;No live shell on the original server during the offline review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The primary site had both filesystem and log evidence. The other four sites had log evidence only.&lt;/p&gt;

&lt;p&gt;That distinction matters. A site with no post-exploitation requests in its access log cannot automatically be declared clean when its filesystem and database have not been examined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial access
&lt;/h2&gt;

&lt;p&gt;The relevant requests were POST requests to either of these routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/?rest_route=/batch/v1
/wp-json/batch/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A large number returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;207 Multi-Status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;207&lt;/code&gt; response is a strong exposure signal because it shows that the batch endpoint exists, is reachable and processed the request. It is not enough by itself to prove that a persistent compromise was completed.&lt;/p&gt;

&lt;p&gt;In this incident, persistent compromise was confirmed by the requests that immediately followed: command execution through newly written PHP files, mailer deployment, directory creation, plugin activation, administrator actions and crontab changes.&lt;/p&gt;

&lt;p&gt;The first confirmed command execution occurred on &lt;strong&gt;19 July at 07:53:57&lt;/strong&gt; through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-content/plugins/media-optimization-core-1fd578/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The attacker's first actions were typical environment reconnaissance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;id
uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;hostname
pwd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs also showed checks for local mail-delivery capabilities, creation of directories under &lt;code&gt;uploads&lt;/code&gt; and &lt;code&gt;mu-plugins&lt;/code&gt;, deployment of a mailer and installation of a crontab entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date and time&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;18 Jul 18:08&lt;/td&gt;
&lt;td&gt;First probes against the batch endpoint on the primary site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;19 Jul 07:53:57&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;First confirmed compromise.&lt;/strong&gt; Commands executed through &lt;code&gt;media-optimization-core-1fd578.php&lt;/code&gt;. The operator checked mail capabilities, deployed a mailer, created directories and added system-cron persistence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19 Jul 12:42&lt;/td&gt;
&lt;td&gt;Database credentials extracted from &lt;code&gt;wp-config.php&lt;/code&gt;; email addresses exfiltrated through &lt;code&gt;mysql&lt;/code&gt; and &lt;code&gt;wp-cli&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19 Jul 16:25 to 17:12&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;class-wp-taxonomy-bd9d4f.php&lt;/code&gt; deployed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22 Jul 10:50&lt;/td&gt;
&lt;td&gt;Scans using the User-Agent &lt;code&gt;wp2shell-rce/1.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;24 Jul 03:50 to 06:22&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;6,849 requests&lt;/strong&gt; to the batch endpoint from &lt;code&gt;152.44.36.152&lt;/code&gt; in 2 hours and 32 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24 Jul 03:55&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sgio-wp2shell-13f838eba615&lt;/code&gt; activated through &lt;code&gt;wp-admin&lt;/code&gt; with a valid nonce, indicating use of an administrator session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24 Jul 03:55:48&lt;/td&gt;
&lt;td&gt;First request to &lt;code&gt;/ganteng.html&lt;/code&gt;; the defacement was public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24 Jul 15:05&lt;/td&gt;
&lt;td&gt;A second operator activated &lt;code&gt;wp2p_fe13d6d7&lt;/code&gt;, which wrote &lt;code&gt;0x.txt&lt;/code&gt; containing &lt;code&gt;chinafans&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26 Jul 09:12 to 10:12&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;_xp_&lt;/code&gt;, &lt;code&gt;_px_&lt;/code&gt; and &lt;code&gt;_vx_&lt;/code&gt; modules deployed. Explicit timestomping performed with &lt;code&gt;touch -r&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26 Jul 10:12&lt;/td&gt;
&lt;td&gt;Malicious &lt;code&gt;.htaccess&lt;/code&gt; added to force PHP execution in the plugin directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;27 Jul 00:52:38&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Wordfence disabled.&lt;/strong&gt; The &lt;code&gt;wordfence&lt;/code&gt; and &lt;code&gt;wflogs&lt;/code&gt; directories were renamed to disabled or backup names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27 Jul 00:52&lt;/td&gt;
&lt;td&gt;Main payload downloaded from an attacker-controlled, defanged domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27 Jul 17:32&lt;/td&gt;
&lt;td&gt;First invocation of &lt;code&gt;feature=healspread&lt;/code&gt;, used for propagation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27 Jul 21:57&lt;/td&gt;
&lt;td&gt;Search for &lt;code&gt;class-wp-rest-compat-85abe7.php&lt;/code&gt; across the server and write tests against common virtual-host roots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28 Jul 10:57&lt;/td&gt;
&lt;td&gt;The 96 KB &lt;code&gt;class-wp-compat_*&lt;/code&gt; shells were redeployed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29 Jul 12:17&lt;/td&gt;
&lt;td&gt;Last observed request to the vulnerable batch endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29 Jul 13:06&lt;/td&gt;
&lt;td&gt;WordPress core updated to &lt;code&gt;7.0.2&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;29 Jul 13:14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;_px_83ac137d.php&lt;/code&gt; rewritten eight minutes after the update&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last line is the one I would keep in mind during cleanup. The patch worked, but existing persistence continued to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separating the two operator clusters
&lt;/h2&gt;

&lt;p&gt;I use "operator cluster" here in a narrow sense. This is not attribution to a person or organisation. It means that the malware, account patterns and cleanup logic let us separate two sets of activity.&lt;/p&gt;

&lt;p&gt;The strongest evidence came from the &lt;code&gt;_vx_&lt;/code&gt; verifier and &lt;code&gt;_xp_&lt;/code&gt; eviction modules. Their code contained explicit lists for "ours" and "rivals".&lt;/p&gt;

&lt;h3&gt;
  
  
  Cluster A: &lt;code&gt;wpsvc&lt;/code&gt; / CoupDeGrace
&lt;/h3&gt;

&lt;p&gt;The account identifier recovered from the &lt;code&gt;ou=&lt;/code&gt; parameter was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wpsvc_9b9b2ba2be34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The associated email used an attacker-controlled internal-looking domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wpsvc_9b9b2ba2be34@wordpress-svc.internal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Artifacts associated with this cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-content/plugins/media-optimization-core-1fd578/
wp-content/mu-plugins/wp-db-optimizer-ce974c.php
wp-content/uploads/class-wp-cache-41d7f8.php
wp-content/cdg.txt
wp-content/plugins/cdg.txt
wp-content/themes/cdg.txt
wp-content/uploads/cdg.txt
ganteng.html
cdg.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_xp_&lt;/code&gt; module acted as an eviction component. It removed administrators whose login matched patterns associated with other operators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp2_
w2s_
wp_svc_
wordpress_[hex12]
bob_[hex12]
Nx_[hex12]
[hex12]
wpadmin[A-Z]{2,}
yun_\d+
support
admin001
adminlin
seomanager
upgrades
super
kacak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also removed accounts using email domains such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp2shell.invalid
wp2shell.local
wordpress-svc.internal
nx.invalid
bobresearchlabs.com
shellcode.lol
local.host
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same component deleted rival must-use plugins and rival plugin directories.&lt;/p&gt;

&lt;p&gt;This is why the filesystem represented only the last stable state of the compromise. Some files visible in the logs had already been deleted by one of the operators.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cluster B: database-backed must-use plugins
&lt;/h3&gt;

&lt;p&gt;The second cluster deployed 15 must-use plugins using plausible WordPress-style names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-asset-loader-*
wp-cache-handler-*
wp-core-update-*
wp-cron-helper-*
wp-health-check-*
wp-mail-handler-*
wp-rest-optimizer-*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These names appeared in Cluster A's explicit rival list.&lt;/p&gt;

&lt;p&gt;This second persistence design was more careful:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The active payload lived outside the webroot in &lt;code&gt;/tmp/php&amp;lt;random&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A backup copy was expected under &lt;code&gt;uploads/class-wp-&amp;lt;type&amp;gt;_&amp;lt;hash&amp;gt;.php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If both files were missing, the must-use plugin rebuilt the binary from a Base64 blob stored in &lt;code&gt;wp_options&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It parsed database credentials directly from &lt;code&gt;wp-config.php&lt;/code&gt; and connected with PDO.&lt;/li&gt;
&lt;li&gt;It scheduled the WordPress cron event &lt;code&gt;wp_cache_health_07ab0b44&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It hid itself from the Must-Use plugin list with &lt;code&gt;show_advanced_plugins&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The relevant database option was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_site_transient_health_07ab0b44
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a useful IOC because deleting the PHP files did not remove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The attacker also patched the entry point
&lt;/h2&gt;

&lt;p&gt;One must-use plugin did not appear in either cluster's ownership list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-rest-hardening-18b216a0.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its purpose was to block &lt;code&gt;/batch/v1&lt;/code&gt; for everyone except requests carrying a secret header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-WP-Site-Token: &amp;lt;redacted-token&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic was equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rest_api_init'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_user_logged_in&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_URI'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/batch/v1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$provided&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'HTTP_X_WP_SITE_TOKEN'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;redacted-token&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$provided&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;hash_equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$provided&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;status_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'code'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'rest_forbidden'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'message'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Sorry, you are not allowed to do that.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operator closed the vulnerable route after gaining access, while keeping a private bypass.&lt;/p&gt;

&lt;p&gt;This creates a bad diagnostic trap. A &lt;code&gt;403&lt;/code&gt; response from the endpoint cannot be accepted as proof that the site is patched. In this case, the &lt;code&gt;403&lt;/code&gt; was produced by malware running inside WordPress.&lt;/p&gt;

&lt;p&gt;The endpoint response has to be interpreted together with the core version, the loaded must-use plugins and the filesystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Malicious file inventory
&lt;/h2&gt;

&lt;p&gt;All confirmed files were isolated before analysis.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Observed mtime&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/plugins/media-optimization-core-1fd578/media-optimization-core-1fd578.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,299 B&lt;/td&gt;
&lt;td&gt;19 Jul 07:53&lt;/td&gt;
&lt;td&gt;Primary backdoor. Accepted a token and a command parameter. The live token is redacted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.../_px_83ac137d.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,755 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29 Jul 13:14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Persistence orchestrator and administrator creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.../class-wp-compat_eca18b00.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;96,546 B&lt;/td&gt;
&lt;td&gt;28 Jul 10:57&lt;/td&gt;
&lt;td&gt;Main shell, obfuscated with &lt;code&gt;chr(N^K)&lt;/code&gt; expressions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.../class-wp-compat_1e9d64c0.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;96,546 B&lt;/td&gt;
&lt;td&gt;28 Jul 10:57&lt;/td&gt;
&lt;td&gt;Identical clone, MD5 beginning &lt;code&gt;8b33c290&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.../class-wp-rest-compat-85abe7.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;96,546 B&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19 Jun 10:50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Identical clone with a falsified mtime matching the core installation date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.../.htaccess&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;126 B&lt;/td&gt;
&lt;td&gt;26 Jul 10:12&lt;/td&gt;
&lt;td&gt;Forced PHP execution inside the plugin directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/uploads/class-wp-cache-41d7f8.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3,755 B&lt;/td&gt;
&lt;td&gt;Not reliable&lt;/td&gt;
&lt;td&gt;Copy of the &lt;code&gt;_px_&lt;/code&gt; dropper, MD5 beginning &lt;code&gt;736d4ef3&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/mu-plugins/wp-db-optimizer-ce974c.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;365 B&lt;/td&gt;
&lt;td&gt;26 Feb 14:10&lt;/td&gt;
&lt;td&gt;Cluster A guardian. Recreated the dropper from the uploads copy and vice versa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/mu-plugins/wp-{asset-loader,cache-handler,core-update,cron-helper,health-check,mail-handler,rest-optimizer}-*.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;About 1,832 B each&lt;/td&gt;
&lt;td&gt;26 Feb 14:10&lt;/td&gt;
&lt;td&gt;15 Cluster B guardians with database-backed recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/mu-plugins/wp-rest-hardening-18b216a0.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;663 B&lt;/td&gt;
&lt;td&gt;26 Feb 14:10&lt;/td&gt;
&lt;td&gt;Blocked &lt;code&gt;/batch/v1&lt;/code&gt; unless a secret header was present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/plugins/sgio-wp2shell-13f838eba615/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2,771 B&lt;/td&gt;
&lt;td&gt;24 Jul 03:54&lt;/td&gt;
&lt;td&gt;Exploit plugin. Exposed a tokenised REST route whose exact route ID is redacted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/plugins/wp2p_fe13d6d7/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;111 B&lt;/td&gt;
&lt;td&gt;24 Jul 15:05&lt;/td&gt;
&lt;td&gt;Wrote &lt;code&gt;0x.txt&lt;/code&gt; containing &lt;code&gt;chinafans&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wp-content/{,plugins/,themes/,uploads/}cdg.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12 B&lt;/td&gt;
&lt;td&gt;24 Jul 03:55&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CoupDeGrace&lt;/code&gt; markers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Timestomping
&lt;/h3&gt;

&lt;p&gt;The mtime &lt;code&gt;26 Feb 2026 14:10&lt;/code&gt; on the 17 malicious must-use plugins was false.&lt;/p&gt;

&lt;p&gt;It matched the mtime of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-includes/plugin.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dropper used logic equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;touch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$mu_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;filemtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ABSPATH&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'wp-includes/plugin.php'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The larger shell used an explicit shell command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ../../../../wp-includes/version.php &amp;lt;malicious-file&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sorting the directory by date would not have found these files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Files present in logs but missing from disk
&lt;/h2&gt;

&lt;p&gt;The following artifacts appeared in requests or command output but were not present in the final filesystem copy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mta-19310704.php
class-wp-taxonomy-bd9d4f.php
_xp_83ac137d.php
_vx_83ac137d.php
class-wp-session-compat-e77b1f.php
class-wp-http-compat-c51985.php
class-wp-cron-handler-0ba61a.php
class-wp-cron-handler-f49b0f.php
class-wp-feed-generator-08dcff.php
class-wp-feed-generator-3e7d3e.php
class-wp-rewrite-core-000e42.php
class-wp-rewrite-core-8d1aca.php
class-wp-rewrite-core-51d39c.php
class-wp-block-renderer-dfcfe8.php
class-wp-locale-helper-e26698.php
class-wp-rest-compat-86f744.php
uploads/class-wp-cache-2582cf10.php
sgio-wp2shell-bd3f8adc83d3/hantu.php
wp2p_fe13d6d7/9df6586309fb.php
wp2shell_e44ffa62/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temporary payload paths observed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/tmp/php7RvENA
/tmp/phppllPkX
/tmp/phpSlkrQv
/tmp/phpK2GRXS
/tmp/phpsHZLfT
/tmp/phpahxTC5
/tmp/phpmK5Ucl
/tmp/phps3vkg9
/tmp/phpAfFsOW
/tmp/phpuoQExw
/tmp/phpG2YQai
/tmp/phppSdonl
/tmp/phpu9B21m
/tmp/phpe92ZoW
/tmp/phps8XYvU
/tmp/phpWhWw2C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Missing from the final copy does not mean irrelevant. In a multi-operator compromise, deleted files can be evidence of operator conflict, previous persistence or an incomplete cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistence outside the site filesystem
&lt;/h2&gt;

&lt;p&gt;This was the main reason a normal "delete the suspicious plugin and update WordPress" cleanup would fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. System-user crontab
&lt;/h3&gt;

&lt;p&gt;A cron entry ran every five minutes and restored a shell if it was missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/5 * * * * test -f &amp;lt;site-root&amp;gt;/class-wp-taxonomy-bd9d4f.php || cp &amp;lt;site-root&amp;gt;/uploads/class-wp-cache-2582cf10.php &amp;lt;site-root&amp;gt;/class-wp-taxonomy-bd9d4f.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checks required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /etc/cron.d/
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/spool/cron/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Database option
&lt;/h3&gt;

&lt;p&gt;The option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_site_transient_health_07ab0b44
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;contained a Base64-encoded payload.&lt;/p&gt;

&lt;p&gt;The malware could reconstruct a deleted binary by reading that option through PDO.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. WordPress cron
&lt;/h3&gt;

&lt;p&gt;The scheduled hook was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp_cache_health_07ab0b44
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cron event list | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'wp_cache_health_07ab0b44'&lt;/span&gt;
wp option get &lt;span class="s1"&gt;'_site_transient_health_07ab0b44'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands should be run only after preserving the database evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;/tmp&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;There were 16 paths matching &lt;code&gt;/tmp/php&amp;lt;random&amp;gt;&lt;/code&gt;, with payloads around 46,976 bytes or larger.&lt;/p&gt;

&lt;p&gt;A narrow first pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find /tmp &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'php*'&lt;/span&gt; &lt;span class="nt"&gt;-size&lt;/span&gt; +40k &lt;span class="nt"&gt;-ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A filename and size filter is only a lead. It is not a verdict.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Backdoor administrator
&lt;/h3&gt;

&lt;p&gt;The database was not available, so the administrator account could not be verified directly.&lt;/p&gt;

&lt;p&gt;The review should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp user list &lt;span class="nt"&gt;--fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ID,user_login,user_email,roles,user_registered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wpsvc_9b9b2ba2be34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also search for the login and email patterns listed in the operator-cluster section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database and mail impact
&lt;/h2&gt;

&lt;p&gt;The logs showed commands that read database credentials from &lt;code&gt;wp-config.php&lt;/code&gt; and then used both &lt;code&gt;mysql&lt;/code&gt; and &lt;code&gt;wp-cli&lt;/code&gt; to export email addresses.&lt;/p&gt;

&lt;p&gt;The targeted data included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;wp_users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wp_comments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;WooCommerce tables containing customer email addresses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database username and password were then passed through a query string during attacker activity.&lt;/p&gt;

&lt;p&gt;That creates a second exposure: web access logs may contain the credentials in plain text. Cleaning the site without protecting, rotating and restricting those logs leaves another copy of the secret behind.&lt;/p&gt;

&lt;p&gt;The mail module was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mta-19310704.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its fallback chain was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;sendmail&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Direct SMTP socket to &lt;code&gt;localhost:25&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;PHP &lt;code&gt;mail()&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The server was being used as a spam relay. Incident handling therefore had to include mail queues, outbound-mail logs, provider reputation and any credentials associated with SMTP delivery.&lt;/p&gt;

&lt;p&gt;The email-address export may constitute a personal-data breach. The legal and notification assessment depends on jurisdiction, role and actual data involved, so it should be handled with the relevant privacy or legal contact rather than guessed from the filesystem alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Log analysis across five sites
&lt;/h2&gt;

&lt;p&gt;Customer-owned domains are replaced with neutral labels.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;IOC hits&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;207&lt;/code&gt; responses&lt;/th&gt;
&lt;th&gt;Source IPs&lt;/th&gt;
&lt;th&gt;Post-exploitation requests&lt;/th&gt;
&lt;th&gt;Observed window&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary site&lt;/td&gt;
&lt;td&gt;7,348&lt;/td&gt;
&lt;td&gt;7,011&lt;/td&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;225&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;18 Jul 18:08 to 29 Jul 12:17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sibling A&lt;/td&gt;
&lt;td&gt;186&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;18 Jul to 29 Jul 03:38&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sibling B&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;18 Jul 22:19 to 25 Jul 21:15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sibling C&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;19 Jul 10:06 to 29 Jul 05:29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sibling D&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;19 Jul 16:19 to 29 Jul 11:04&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Only the primary site showed clear post-exploitation traffic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests to shell paths&lt;/li&gt;
&lt;li&gt;Parameters such as &lt;code&gt;?px=&lt;/code&gt;, &lt;code&gt;?ou=&lt;/code&gt; and &lt;code&gt;?sp=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Plugin activation through &lt;code&gt;wp-admin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Requests to the defacement&lt;/li&gt;
&lt;li&gt;Interactive command activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other four sites showed traffic to the batch endpoint, but no requests to known shell paths and no plugin-activation sequence.&lt;/p&gt;

&lt;p&gt;That is not enough to declare them clean.&lt;/p&gt;

&lt;p&gt;All four returned &lt;code&gt;207&lt;/code&gt; at least once. Their response sizes also followed the same progression as the primary site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Baseline: about 6.35 KB
Second stage: baseline + about 463 bytes
Later stage: baseline + about 610 to 620 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The consistent size changes suggest that the chain progressed through at least two stages on all five sites.&lt;/p&gt;

&lt;p&gt;Without their filesystems and databases, the correct conclusion is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Exposed and potentially exploited, with no confirmed post-exploitation in the available logs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those sites still required patching, filesystem review, database checks and YARA scanning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-vhost activity
&lt;/h2&gt;

&lt;p&gt;Several source IPs attacked more than one customer-owned domain in the same hosting environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;45.138.16.164
51.161.51.84
179.43.145.34
209.99.186.3
216.126.227.x
104.194.146.x
172.81.132.79
156.0.200.13x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A filesystem spread attempt was documented from the primary site on 27 July at 21:57.&lt;/p&gt;

&lt;p&gt;The operator used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feature=healspread
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then searched for a known shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find / &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'class-wp-rest-compat-85abe7.php'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write tests targeted common hosting paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/www/html/
/home/*/public_html/
/srv/www/
/var/www/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generic paths failed except for the real site path. There was no log evidence that another virtual host was successfully written.&lt;/p&gt;

&lt;p&gt;That is a limited statement. It means "not observed", not "impossible".&lt;/p&gt;

&lt;h2&gt;
  
  
  Main source IPs
&lt;/h2&gt;

&lt;p&gt;The main IPs observed against the primary site were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;152.44.36.152    6,849 requests
173.231.200.13     122 requests, most interactive activity
92.189.77.250
172.235.128.108
216.170.192.140
34.171.141.44     first confirmed compromise
34.172.185.49
162.193.207.171
34.136.134.112
34.171.169.105
156.0.200.134
195.206.105.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several addresses belonged to large cloud providers or low-cost VPS ranges.&lt;/p&gt;

&lt;p&gt;Blocking them can reduce noise, but it is not remediation. The exploit traffic came from many addresses, and the persistence no longer depended on the original source IP.&lt;/p&gt;

&lt;h2&gt;
  
  
  User-Agent indicators
&lt;/h2&gt;

&lt;p&gt;High-fidelity strings that did not appear in legitimate traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp2shell
wp2shell-rce/1.0
wp2shell-check/1.0
wp2shell-checker
Mozilla/5.0 (compatible; wp2shell-check/1.0)
Mozilla/5.0 (compatible; SiteHealth/1.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Medium-fidelity strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Mozilla/5.0
python-requests/2.34.2
Python-urllib/3.13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first Windows string is suspicious because it stops after &lt;code&gt;AppleWebKit/537.36&lt;/code&gt; and lacks the normal browser suffix.&lt;/p&gt;

&lt;p&gt;Medium-fidelity indicators should be combined with route, method, response code, source and request timing. Used alone, they will produce false positives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filesystem coverage
&lt;/h2&gt;

&lt;p&gt;The full primary-site tree was scanned recursively with YARA.&lt;/p&gt;

&lt;p&gt;Directories reviewed explicitly included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;primary-site&amp;gt;/                              site root
&amp;lt;primary-site&amp;gt;/.well-known/
&amp;lt;primary-site&amp;gt;/.wp-cli/
&amp;lt;primary-site&amp;gt;/cgi-bin/
&amp;lt;primary-site&amp;gt;/wp-admin/                     including includes/
&amp;lt;primary-site&amp;gt;/wp-includes/                  including rest-api/endpoints/
&amp;lt;primary-site&amp;gt;/wp-content/
&amp;lt;primary-site&amp;gt;/wp-content/blogs.dir/
&amp;lt;primary-site&amp;gt;/wp-content/cache/
&amp;lt;primary-site&amp;gt;/wp-content/languages/
&amp;lt;primary-site&amp;gt;/wp-content/litespeed/
&amp;lt;primary-site&amp;gt;/wp-content/maintenance/
&amp;lt;primary-site&amp;gt;/wp-content/mu-plugins/        17 confirmed malicious files out of 19
&amp;lt;primary-site&amp;gt;/wp-content/plugins/           31 subdirectories reviewed
&amp;lt;primary-site&amp;gt;/wp-content/themes/
&amp;lt;primary-site&amp;gt;/wp-content/updraft/
&amp;lt;primary-site&amp;gt;/wp-content/upgrade/
&amp;lt;primary-site&amp;gt;/wp-content/upgrade-temp-backup/
&amp;lt;primary-site&amp;gt;/wp-content/uploads/           complete tree, 72 PHP files
&amp;lt;primary-site&amp;gt;/wp-content/wflogs/
&amp;lt;primary-site&amp;gt;/wp-content/wflogs.bak_1785106358/
&amp;lt;logs&amp;gt;/sibling-a/
&amp;lt;logs&amp;gt;/sibling-b/
&amp;lt;logs&amp;gt;/primary/
&amp;lt;logs&amp;gt;/sibling-c/
&amp;lt;logs&amp;gt;/sibling-d/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Negative findings and false positives
&lt;/h2&gt;

&lt;p&gt;Recording what was checked and found clean was useful. Otherwise every later reviewer has to repeat the same work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Themes
&lt;/h3&gt;

&lt;p&gt;No theme PHP file had been modified in July 2026.&lt;/p&gt;

&lt;p&gt;The only malicious theme artifact was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-content/themes/cdg.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Uploads
&lt;/h3&gt;

&lt;p&gt;There were 72 PHP files under &lt;code&gt;wp-content/uploads/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of those:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;71 were legitimate WPML Twig cache files under &lt;code&gt;uploads/cache/wpml/twig/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;1 was malicious: &lt;code&gt;class-wp-cache-41d7f8.php&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rule saying "every PHP file in uploads is malware" would have produced 71 false positives here.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.htaccess&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Seven &lt;code&gt;.htaccess&lt;/code&gt; files were present:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Six were legitimate, belonging to LiteSpeed, Wordfence, UpdraftPlus or WooCommerce&lt;/li&gt;
&lt;li&gt;One was malicious and forced PHP execution in the attacker's plugin directory&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Drop-ins and host automation
&lt;/h3&gt;

&lt;p&gt;These were legitimate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-content/object-cache.php
wp-content/maintenance.php
wp-content/mu-plugins/automation-by-installatron.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hidden-file patterns
&lt;/h3&gt;

&lt;p&gt;No remaining files matched the attacker's previously used hidden-name styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.tmp_*
.??????????.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs indicated that some were removed before the filesystem was copied.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core and legitimate plugins
&lt;/h3&gt;

&lt;p&gt;No webshell was confirmed inside WordPress core or legitimate plugin files.&lt;/p&gt;

&lt;p&gt;YARA hits in these locations were reviewed as false positives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wp-admin/includes/file.php
litespeed-cache
updraftplus/central/modules/core.php
woocommerce
wordpress-seo
wp-includes/rest-api*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rules matched legitimate combinations such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;register_rest_route
permission_callback
__return_true
base64_decode
$_POST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every hit had to be opened and reviewed. A scanner result was treated as a queue, not as a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical detection workflow
&lt;/h2&gt;

&lt;p&gt;These are the first checks I would run on another WordPress site suspected of the same compromise.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Record the running version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp core version
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;wp_version"&lt;/span&gt; wp-includes/version.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Inspect must-use plugins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; wp-content/mu-plugins/
find wp-content/mu-plugins/ &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.php'&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for plausible core-style names followed by hexadecimal suffixes.&lt;/p&gt;

&lt;p&gt;Do not trust the modification time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Inspect PHP files in uploads
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find wp-content/uploads/ &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.php'&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review context before deleting anything. Cache plugins and translation plugins can create legitimate PHP files in specific subdirectories.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Inspect unexpected plugins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find wp-content/plugins/ &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-printf&lt;/span&gt; &lt;span class="s1"&gt;'%f\n'&lt;/span&gt; | &lt;span class="nb"&gt;sort
&lt;/span&gt;wp plugin list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare with deployment records or a known-good inventory.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Search for specific artifacts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find wp-content/ &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'cdg.txt'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'ganteng.html'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'class-wp-cache-*.php'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'class-wp-compat_*.php'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'wp-rest-hardening-*.php'&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Search logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s1"&gt;'batch/v1'&lt;/span&gt; /path/to/logs/
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s1"&gt;'wp2shell'&lt;/span&gt; /path/to/logs/
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'(\?px=|\?ou=|\?sp=)'&lt;/span&gt; /path/to/logs/
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s1"&gt;'plugins.php?action=activate'&lt;/span&gt; /path/to/logs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also inspect &lt;code&gt;207&lt;/code&gt;, &lt;code&gt;403&lt;/code&gt; and unusual response-size clusters.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Check persistence outside WordPress files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /etc/cron.d/
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/spool/cron/
find /tmp &lt;span class="nt"&gt;-maxdepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'php*'&lt;/span&gt; &lt;span class="nt"&gt;-ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then review WordPress cron and the suspicious option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp cron event list
wp option get &lt;span class="s1"&gt;'_site_transient_health_07ab0b44'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Review users and sessions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp user list &lt;span class="nt"&gt;--fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ID,user_login,user_email,roles,user_registered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review recent administrators, unexpected email domains and accounts matching the known patterns.&lt;/p&gt;

&lt;p&gt;After evidence collection, invalidate all sessions and rotate WordPress salts.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Run YARA
&lt;/h3&gt;

&lt;p&gt;The case-specific ruleset contained 11 rules and was executed recursively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yara &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; rules/wp2shell.yar /path/to/public_html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful ruleset should cover more than exact hashes. In this incident, filenames, marker strings, option names, cron-hook names, obfuscation style and recovery logic were more durable than one file hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup order
&lt;/h2&gt;

&lt;p&gt;The order matters because WordPress requests can load must-use plugins, and must-use plugins can recreate deleted files.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Contain the site
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Remove it from public traffic or place it behind a controlled maintenance response&lt;/li&gt;
&lt;li&gt;Preserve the filesystem, database and logs&lt;/li&gt;
&lt;li&gt;Record hashes and timestamps before modifying evidence&lt;/li&gt;
&lt;li&gt;Stop normal visitors, health checks and cron-triggering requests from executing the compromised WordPress stack&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Stop persistence outside the webroot
&lt;/h3&gt;

&lt;p&gt;Check and remove confirmed malicious entries from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System-user crontab&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/cron.d/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/var/spool/cron/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/tmp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wp_options&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;WordPress cron&lt;/li&gt;
&lt;li&gt;Unexpected administrator accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do this before deleting the visible shells.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rotate secrets
&lt;/h3&gt;

&lt;p&gt;At minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database password&lt;/li&gt;
&lt;li&gt;Hosting control-panel credentials&lt;/li&gt;
&lt;li&gt;SSH or SFTP credentials&lt;/li&gt;
&lt;li&gt;WordPress administrator passwords&lt;/li&gt;
&lt;li&gt;WordPress salts&lt;/li&gt;
&lt;li&gt;SMTP credentials&lt;/li&gt;
&lt;li&gt;API keys stored in &lt;code&gt;wp-config.php&lt;/code&gt;, plugin settings or environment files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also invalidate active WordPress sessions.&lt;/p&gt;

&lt;p&gt;Because the database password appeared in access logs, protect or rotate access to the logs themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rebuild from known-good sources
&lt;/h3&gt;

&lt;p&gt;A clean rebuild is safer than deleting files one by one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace WordPress core with a verified patched release&lt;/li&gt;
&lt;li&gt;Reinstall plugins from trusted packages&lt;/li&gt;
&lt;li&gt;Reinstall themes from trusted packages&lt;/li&gt;
&lt;li&gt;Restore only reviewed uploads&lt;/li&gt;
&lt;li&gt;Recreate configuration files manually where practical&lt;/li&gt;
&lt;li&gt;Compare with a known-good inventory&lt;/li&gt;
&lt;li&gt;Remove every unknown must-use plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not copy the old &lt;code&gt;wp-content&lt;/code&gt; directory back as a single unit.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Patch every related site
&lt;/h3&gt;

&lt;p&gt;The four sibling sites returned &lt;code&gt;207&lt;/code&gt; and showed multi-stage response-size changes.&lt;/p&gt;

&lt;p&gt;Even without confirmed post-exploitation, they required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A patched WordPress version&lt;/li&gt;
&lt;li&gt;Filesystem scan&lt;/li&gt;
&lt;li&gt;Database-option review&lt;/li&gt;
&lt;li&gt;User review&lt;/li&gt;
&lt;li&gt;Cron review&lt;/li&gt;
&lt;li&gt;Log review&lt;/li&gt;
&lt;li&gt;Secret rotation based on shared hosting and credential reuse&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Review data and mail impact
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Preserve evidence of email-address extraction&lt;/li&gt;
&lt;li&gt;Review WooCommerce customer-data exposure&lt;/li&gt;
&lt;li&gt;Check outbound mail queues and SMTP logs&lt;/li&gt;
&lt;li&gt;Check blocklists and provider abuse notices&lt;/li&gt;
&lt;li&gt;Start the appropriate privacy and legal assessment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Validation after cleanup
&lt;/h2&gt;

&lt;p&gt;A clean home page proves very little.&lt;/p&gt;

&lt;p&gt;I used active checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guardian test
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Record the hash and location of every malicious shell.&lt;/li&gt;
&lt;li&gt;Remove the confirmed persistence mechanism.&lt;/li&gt;
&lt;li&gt;Delete the shell.&lt;/li&gt;
&lt;li&gt;Trigger controlled requests to the site several times.&lt;/li&gt;
&lt;li&gt;Run WordPress cron manually in the controlled environment.&lt;/li&gt;
&lt;li&gt;Wait past the previous five-minute crontab interval.&lt;/li&gt;
&lt;li&gt;Check whether the file, database option, cron event or &lt;code&gt;/tmp&lt;/code&gt; payload returns.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The test should be done in an isolated or maintenance environment, with logs enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoint validation
&lt;/h3&gt;

&lt;p&gt;Confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The WordPress core version is patched&lt;/li&gt;
&lt;li&gt;No unknown must-use plugin intercepts &lt;code&gt;/batch/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The secret-header bypass is gone&lt;/li&gt;
&lt;li&gt;The response comes from the patched core or an approved security control&lt;/li&gt;
&lt;li&gt;No attacker-created route remains registered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A status code alone is not enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrity validation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Compare WordPress core checksums&lt;/li&gt;
&lt;li&gt;Reinstall and compare plugins&lt;/li&gt;
&lt;li&gt;Reinstall and compare themes&lt;/li&gt;
&lt;li&gt;Review every PHP file under uploads&lt;/li&gt;
&lt;li&gt;Review every file under &lt;code&gt;mu-plugins&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Re-run YARA&lt;/li&gt;
&lt;li&gt;Re-run IOC searches across logs&lt;/li&gt;
&lt;li&gt;Review recent administrator creation&lt;/li&gt;
&lt;li&gt;Confirm crontab, &lt;code&gt;/tmp&lt;/code&gt;, database option and WP-Cron persistence are absent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;For the following days, alert on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;batch/v1
wp2shell
CoupDeGrace
cdg.txt
ganteng.html
wp_cache_health_07ab0b44
_site_transient_health_07ab0b44
class-wp-cache-
class-wp-compat_
wp-rest-hardening-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also alert on unexpected plugin activation, new administrator creation and PHP execution from uploads.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this incident changed in my review process
&lt;/h2&gt;

&lt;p&gt;A few checks moved permanently to the top of my WordPress incident-response list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect &lt;code&gt;mu-plugins&lt;/code&gt; before the normal plugin list.&lt;/li&gt;
&lt;li&gt;Inspect system cron before deleting files.&lt;/li&gt;
&lt;li&gt;Search &lt;code&gt;wp_options&lt;/code&gt; for encoded payloads and suspicious transient names.&lt;/li&gt;
&lt;li&gt;Treat mtimes as attacker-controlled metadata.&lt;/li&gt;
&lt;li&gt;Review access logs for secrets passed through query strings.&lt;/li&gt;
&lt;li&gt;Do not use a &lt;code&gt;403&lt;/code&gt; response as proof that a vulnerable route is safe.&lt;/li&gt;
&lt;li&gt;Do not declare sibling sites clean from logs alone.&lt;/li&gt;
&lt;li&gt;Treat an update as containment of the vulnerability, not removal of the compromise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The defacement was the least important artifact in the whole incident. It was just the point at which the attackers became visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rafaelcosquiere.com/en/notes/hacked-by-coupdegrace-wordpress-hacked-wp2shell/" rel="noopener noreferrer"&gt;My less technical write-up of the incident&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/news/2026/07/wordpress-7-0-2-release/" rel="noopener noreferrer"&gt;WordPress 7.0.2 security release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-ff9f-jf42-662q" rel="noopener noreferrer"&gt;GHSA-ff9f-jf42-662q: REST API batch-route confusion and RCE chain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/WordPress/wordpress-develop/security/advisories/GHSA-fpp7-x2x2-2mjf" rel="noopener noreferrer"&gt;GHSA-fpp7-x2x2-2mjf: &lt;code&gt;author__not_in&lt;/code&gt; SQL injection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-63030" rel="noopener noreferrer"&gt;CVE-2026-63030 at NVD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2026-60137" rel="noopener noreferrer"&gt;CVE-2026-60137 at NVD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wp2shell</category>
      <category>wordpress</category>
      <category>security</category>
      <category>incidentresponse</category>
    </item>
  </channel>
</rss>
