WordPress Security Incident WordPress 7.0

WordPress Security Incident: Investigation, Findings, and Remediation

Incident period: July 17–20, 2026

Environment: Shared FreeBSD-hosted WordPress installation serving multiple independent websites

Document Version: 1.2

Last Updated: July 20, 2026

Executive Summary

A shared WordPress installation hosted on FreeBSD was compromised through a WordPress core vulnerability involving the REST API batch endpoint. Crafted input reached WP_Query without proper sanitization, producing SQL injection. The attacker used the vulnerable endpoint for reconnaissance, attempted to write a PHP web shell, created multiple unauthorized WordPress administrator accounts, logged in through wp-login.php, uploaded a malicious plugin, and executed operating-system commands.

The investigation began with an unexplained administrator account and expanded into a correlation of WordPress database records, Wordfence data, Apache access logs, the WordPress debug log, and filesystem evidence. Because all hosted sites shared the same WordPress core and plugin tree, every virtual host was treated as potentially exposed.

The principal remediation was upgrading the shared WordPress installation to the WordPress 7.0 security release line, removing unauthorized accounts and malicious files, resetting administrator credentials, rotating all WordPress authentication keys and salts, invalidating active sessions, and conducting a cross-site review for related persistence.

Hosting Architecture

The affected installation used a shared WordPress codebase:

/usr/local/www/wordpress

WordPress core, plugins, and must-use plugins were shared. Each site retained a separate database and site-specific configuration under:

/usr/local/www/wordpress/wp-content/multiblog/config

This architecture meant that a vulnerable WordPress core file or shared plugin could expose multiple sites even though their databases were separate.

Initial Indicators of Compromise

The first confirmed indicator was unauthorized administrator accounts:

wpsvc_ed5e31a5e660

It used an internal-looking email address similar to:

wp*******@***********vc.internal

A later review identified another unauthorized administrator account:

ID: 2193
Login: wpsvc_eb5f7c65f4e5
Email: wp****************@***********vc.internal
Registered: 2026-07-20 10:00:43 UTC
Capabilities: bbp_participant, administrator
User level: 10

The account was removed from the Gossamer site. A corresponding account on YAWS was demoted while the investigation continued.

A second unauthorized administrator was later discovered:

User ID: 785
Login: wp2_5b18e2
Email: wp********@******ll.invalid
Registered: 2026-07-19 23:09:55 UTC

The associated WordPress metadata confirmed that this was a fully initialized administrator:

gwd_capabilities = a:1:{s:13:"administrator";b:1;}
gwd_user_level   = 10

Apache Access-Log Reconstruction

A follow-on attack sequence was recorded on July 20. The source address
141.98.11.182 issued three POST requests against both forms of the
REST batch endpoint between 02:16:30 and 02:16:34 PDT. All returned HTTP
207 Multi-Status. The final response was approximately 656 KB,
showing that WordPress processed a substantial batched request.

Local time Source Request Result
02:16:30 PDT 141.98.11.182 POST /wp-json/batch/v1 HTTP 207, 16 bytes
02:16:32 PDT 141.98.11.182 POST /?rest_route=/batch/v1 HTTP 207, 16 bytes
02:16:34 PDT 141.98.11.182 POST /?rest_route=/batch/v1 HTTP 207, 656,475 bytes

The unauthorized account wpsvc_eb5f7c65f4e5 was registered at
03:00:43 PDT, approximately 44 minutes after this sequence began. The timing
strongly correlates the account with the exploitation window, although the
logs do not by themselves prove the precise internal account-creation method.

Apache logs showed a complete post-compromise sequence rather than a simple failed probe:

  1. Requests to the WordPress REST batch endpoint
  2. Successful HTTP 207 Multi-Status responses
  3. Administrator login activity
  4. Access to the plugin upload interface
  5. Upload and activation of a malicious plugin
  6. Execution of an operating-system command through a query parameter

The malicious plugin directory was:

wp2s-602d53d5

The attacker then executed a command using a request similar to:

?c=id

The malicious plugin was manually removed after discovery. No surviving copy was found during the later filesystem review.

Wordfence Database Review

The investigation examined Wordfence tables including:

${id}_wflogins
${id}_wfauditevents
${id}_wfissues
${id}_wfsecurityevents

The earliest observed successful login came from an attacker-controlled IP address without a preceding failed-login sequence. That initially suggested the attacker already possessed a valid password or had created a working account before the recorded login. Later evidence showed that the REST exploit could create an administrator account, which better explained the successful login without password guessing.

REST Batch Endpoint Attack Campaign

A server-wide search found repeated requests to both forms of the WordPress REST batch endpoint:

POST /wp-json/batch/v1
POST /?rest_route=/batch/v1

The campaign targeted multiple hosted domains, including [REDACTED], [REDACTED], [REDACTED], [REDACTED], [REDACTED], [REDACTED], [REDACTED], [REDACTED], and [REDACTED].

Some addresses generated only reconnaissance probes. Others made repeated requests and received large 207 responses, indicating that WordPress processed the batch requests. Several requests used the user agent:

wp2shell
wp2shell/3.0

This tied the attack tooling to the naming observed in the malicious account and plugin artifacts.

SQL Injection Evidence

The WordPress debug log contained direct evidence that malicious input reached the SQL layer. The injected condition appeared in queries similar to:

${id}_posts.post_author NOT IN (0)
AND 1=0
UNION SELECT ...

The corresponding call stack repeatedly included:

WP_REST_Server->serve_batch_request_v1
WP_REST_Posts_Controller->get_items
WP_Query->get_posts

This demonstrated that the exploit entered through the REST batch route, reached the posts controller, and altered SQL generated by WP_Query.

Attempted PHP Web-Shell Deployment

The attacker attempted to write a PHP command shell using SQL INTO OUTFILE. A randomized payload filename was generated by the exploit framework:

<randomized-payload>.php

Rather than adapting to the target system, the exploit framework attempted to
write its payload into a series of hard-coded web-accessible locations based on
assumptions about the operating system and directory layout. Those assumptions
did not match the actual deployment.

The embedded PHP supported command-execution functions including system, passthru, exec, shell_exec, and popen. Command output would have been wrapped between [S] and [E].

These web-shell writes failed. The server runs FreeBSD and WordPress was installed under /usr/local/www/wordpress, so the assumed Linux paths were invalid. Immediate requests for the shell returned HTTP 404 Not Found, and no matching file was located.

Administrator Account Creation Timeline

Local Time UTC Observed Event
16:09:19 23:09:19 Initial request to the website
16:09:22–29 23:09:22–29 Multiple REST batch requests returned HTTP 207
16:09:27–30 23:09:27–30 SQL injection attempted three Linux-path OUTFILE writes
16:09:32 23:09:32 Attacker tested the PHP shell and received 404
16:09:41–57 23:09:41–57 Second REST batch exploitation sequence
16:09:54 23:09:54 Complex injected SQL reached WordPress internal processing
16:09:54 23:09:54 wp_insert_user() warning recorded
16:09:55 23:09:55 Unauthorized account wp2_5b18e2 registered
16:09:59–16:10:00 23:09:59–23:10:00 Requests to wp-login.php were blocked with HTTP 403

The database metadata showed that WordPress and installed plugins fully initialized the new user. The account received normal administrator capabilities and a plugin-generated member object. This strongly indicated that the exploit triggered WordPress user-creation logic and associated hooks rather than merely inserting isolated rows directly into MariaDB.

Identification of the Root Cause

The initial working theory was that a vulnerable third-party plugin had provided the SQL injection path. That assessment changed after comparing the observed call stack and SQL payload with the WordPress security fixes released for the affected versions.

WordPress REST batch-route confusion
        ↓
Unsanitized author exclusion value
        ↓
Value reaches author__not_in
        ↓
WP_Query SQL injection
        ↓
Data extraction, account creation, or attempted file write
        ↓
Administrator access
        ↓
Malicious plugin upload
        ↓
Operating-system command execution

The attack pattern matched the WordPress core vulnerability fixed in the later 6.9 maintenance release and the WordPress 7.0 security release line. The shared installation was therefore upgraded to WordPress 7.0 rather than relying only on plugin removal or firewall rules.

Filesystem and Persistence Review

The filesystem investigation searched for the removed plugin, the randomized PHP shell, unexpected PHP files in cache and upload directories, PHAR payloads, recently modified files, and common shell-function patterns.

No surviving web shell, malicious PHAR, or matching payload was found. The malicious plugin had already been removed manually. This reduced the evidence of persistent filesystem compromise, but it did not eliminate the need to assume that administrator credentials and sessions had been exposed.

Database Preservation and Unauthorized-User Removal

Before deleting the unauthorized user, the affected ${id}_users and ${id}_usermeta rows were preserved with mysqldump and hashed with SHA-256.

The user was then removed through WP-CLI rather than by manually deleting database rows:

wp user delete 785 \
  --yes \
  --url=https://[REDACTED] \
  --path=/usr/local/www/wordpress

Using WordPress for deletion allowed normal cleanup hooks to run and reduced the risk of leaving orphaned metadata.

Authentication-Key and Salt Rotation

The WordPress authentication keys and salts were not stored in the standard wp-config.php. They were defined centrally in:

/usr/local/www/wordpress/wp-content/multiblog/config/mb-autoconfig.php

The eight active values were:

$vmb_const['AUTH_KEY']
$vmb_const['SECURE_AUTH_KEY']
$vmb_const['LOGGED_IN_KEY']
$vmb_const['NONCE_KEY']
$vmb_const['AUTH_SALT']
$vmb_const['SECURE_AUTH_SALT']
$vmb_const['LOGGED_IN_SALT']
$vmb_const['NONCE_SALT']

All eight were replaced with newly generated random values. Because the keys were shared through the central Multiblog configuration, this invalidated active WordPress cookies and sessions across all hosted sites at once, including potentially stolen administrator and Remember Me cookies.

WordPress Core Upgrade

The most important technical remediation was updating the shared WordPress core to the WordPress 7.0 release line containing the security fix.

Core integrity was then checked with WP-CLI:

wp core version
wp core verify-checksums

Because every hosted site used the same WordPress core, this single upgrade removed the vulnerable code path for all sites sharing the installation.

Additional Remediation

  • Removed or demoted unauthorized administrator accounts
  • Reset passwords for all legitimate WordPress administrators
  • Rotated all WordPress authentication keys and salts
  • Invalidated all active WordPress sessions
  • Verified WordPress core checksums
  • Searched all virtual-host access logs for REST batch exploitation
  • Searched the WordPress debug log for SQL injection and file-write payloads
  • Searched databases for suspicious users, metadata, options, cron entries, and content
  • Reviewed active and recently activated plugins
  • Searched the filesystem for web shells, malicious plugins, and recently changed PHP files
  • Preserved critical evidence files and generated SHA-256 hashes

Apache Blocking and Fail2Ban Compensating Controls

After the compromise was identified, Apache was configured to reject
requests targeting the vulnerable REST batch endpoint. A dedicated Fail2Ban
jail named wordpress-exploit was added to detect repeated attempts
in the virtual-host access logs and block the source address.

A subsequent attempt from 80.96.109.64 demonstrated that the
controls were operating as intended. Six POST requests to
/?rest_route=/batch/v1 returned HTTP 400 Bad Request,
rather than the HTTP 207 responses associated with successful application
processing. Fail2Ban then recorded:

[wordpress-exploit] Ban 80.96.109.64

The jail uses a one-year ban period. This makes the current Fail2Ban banned
address list useful as a durable IOC source, although the Apache and Fail2Ban
logs remain the authoritative historical evidence.

The active Fail2Ban banned-address list now serves as the authoritative source
for the standalone IOC report. As new exploit attempts are detected, the IOC
report can be regenerated without modifying this incident report, preserving
the report as a point-in-time record while allowing the IOC inventory to
continue evolving.

Final Assessment

The evidence supports a high-confidence conclusion that the shared WordPress installation was compromised through a WordPress core REST batch-route SQL injection vulnerability.

The compromise progressed beyond scanning:

  • The vulnerable SQL path was reached.
  • Web-shell writes were attempted.
  • An unauthorized administrator was created.
  • The attacker obtained authenticated administrator access.
  • A malicious plugin was uploaded and activated.
  • Operating-system commands were executed through that plugin.

The attempted SQL-based web-shell deployment failed because the attack tooling assumed Linux filesystem paths on a FreeBSD server. However, that failure did not prevent the attacker from establishing administrator-level access through WordPress itself.

Upgrading WordPress core, deleting unauthorized accounts, removing the malicious plugin, resetting administrator credentials, rotating authentication keys and salts, invalidating sessions, and inspecting every hosted site collectively addressed both the known compromise and the shared infrastructure exposure.

Lessons Learned

  • A successful HTTP 207 response from the WordPress REST batch endpoint should be treated as processed application traffic, not dismissed as a failed probe.
  • Repeated HTTP 400 responses after the Apache rule was deployed demonstrate rejection of the exploit path before normal batch processing.
  • Fail2Ban correlation across per-site Apache logs provides an effective mechanism for maintaining a continuously updated IOC inventory. Publishing the IOC report as a separate document simplifies future incident reporting and forensic analysis.
  • Separate databases do not fully isolate sites that share one WordPress core and plugin tree.
  • WordPress debug logs can preserve SQL injection payloads even when the resulting SQL statement fails.
  • Invalid filesystem assumptions can stop one exploitation technique while leaving other attack paths available.
  • Unauthorized WordPress users should be preserved for evidence before deletion.
  • Password resets alone do not invalidate stolen WordPress cookies; authentication keys and salts must also be rotated.
  • Following a core vulnerability disclosure, updating WordPress core is essential. Removing visible malware alone does not close the original entry point.
Prepared by: Gossamer Computer Services

This report documents the investigation, evidence, findings, and remediation
completed during the incident response. Sensitive information including
credentials, authentication secrets, database passwords, and complete exploit
payloads has been intentionally omitted.

Appendix A – Forensic Timeline

Approx. Time (UTC) Event
23:09:19 Initial request to the site.
23:09:22–23:09:29 Multiple REST batch endpoint requests returned HTTP 207.
23:09:27–23:09:30 SQL injection attempted multiple INTO OUTFILE web-shell writes using Linux filesystem paths.
23:09:32 Attacker requested the expected shell and received HTTP 404.
23:09:41–23:09:57 Second wave of REST batch exploitation.
23:09:54 Complex SQL payload reached WP_Query; wp_insert_user() warning logged.
23:09:55 Unauthorized administrator account registered.
Following minutes Administrator login, malicious plugin upload, and operating-system command execution.
Incident response Evidence preserved, logs correlated, malicious artifacts removed, unauthorized account deleted.
Recovery Authentication keys and salts rotated, administrator passwords reset, WordPress upgraded to the secured 7.0 release.

Appendix B – Indicators of Compromise (IOCs)

The complete Indicators of Compromise (IOC) inventory generated during this
investigation is maintained as a separate document. The report is produced
from the wordpress-exploit Fail2Ban jail and correlated with the
Apache virtual-host access logs.

Maintaining the IOC data separately allows the incident report to remain
focused on the investigation while permitting the IOC inventory to be
regenerated as additional attack activity is detected.

IOC Report:

WordPress Exploit – Indicators of Compromise (IOC) Report

The standalone IOC report includes:

  • Source IP addresses
  • First and last observed activity
  • Apache log correlation
  • Requested URLs
  • HTTP status codes
  • User-Agent strings
  • Fail2Ban ban status
  • Geographic information (when available)
  • Investigation notes