Back to Intelligence

The Events Calendar WordPress Plugin Unauthenticated RCE: Detection and Remediation Guide for 200,000+ Exposed Sites

SA
Security Arsenal Team
September 16, 2026
12 min read

If you run WordPress at any meaningful scale, there is a nontrivial chance The Events Calendar plugin is installed somewhere in your estate — including on marketing microsites and forgotten staging servers that never make it into your asset inventory. A newly disclosed set of vulnerabilities in the plugin, which has more than 200,000 active installations, can give an unauthenticated remote attacker code execution on the underlying web server. In practical terms: no credentials, no user interaction, direct path to a webshell, and from there full site takeover, database theft, and lateral movement into whatever the web server can reach.

WordPress plugin flaws of this class are the single most common initial access vector we see in incident response engagements involving small and mid-market organizations. Attackers operationalize unauthenticated WordPress RCEs within hours to days of public disclosure — scanning the internet for vulnerable plugin fingerprints is a solved, commoditized problem. The window between disclosure and mass exploitation is effectively zero. If The Events Calendar is in your environment, treat this as an active patch emergency, not a routine update.

Technical Analysis

Affected Product and Exposure

  • Product: The Events Calendar — a widely deployed WordPress plugin used for event management and calendar functionality
  • Install base: 200,000+ active installations per the plugin's public listing, with real-world exposure likely higher when counting unmaintained and staging deployments
  • Attack surface: Any WordPress instance with a vulnerable version of the plugin active and reachable over HTTP/HTTPS — which, by design, is all of them

How the Vulnerability Works (Defender's View)

The reported flaws allow an unauthenticated attacker to achieve code execution through the plugin's request-handling logic. From a defensive standpoint, the anatomy of exploitation follows a well-worn WordPress pattern:

  1. Reconnaissance: The attacker identifies the plugin's presence via publicly observable artifacts — plugin-specific paths such as /wp-content/plugins/the-events-calendar/, enqueued CSS/JS handles, or the plugin's REST API and AJAX endpoints.
  2. Weaponized request: A crafted HTTP request (typically a POST to admin-ajax.php, a plugin REST route under /wp-json/, or a plugin-specific endpoint) reaches code paths that fail to properly validate, sanitize, or authorize the input.
  3. Code execution: The payload executes in the context of the web server process — typically www-data, apache, or nginx on Linux hosts, or the PHP worker pool under php-fpm. This is not full root, but it is more than enough: attackers can read wp-config.php (which contains database credentials), write files anywhere PHP has write access, and execute system commands.
  4. Persistence: The near-universal next step is dropping a PHP webshell into a writable, web-accessible directory — most commonly wp-content/uploads/ — followed by uploading additional tooling, creating rogue admin users, or injecting malicious code into theme files (functions.php is a perennial favorite) to survive plugin updates.

Exploitation Status

At the time of writing, the vulnerabilities have been publicly disclosed and the attack surface is internet-facing by default. For unauthenticated WordPress plugin RCEs with a six-figure install base, defenders should assume weaponization is imminent or already underway regardless of whether public proof-of-concept code has been observed yet. Historically, exploit kits and opportunistic scanners incorporate flaws of this profile within days. Do not wait for confirmation of in-the-wild exploitation to patch — by then, the scanning has already started.

Detection & Response

The detections below target the behavioral artifacts of exploitation rather than request signatures that will change as payloads evolve. That is deliberate: the durable signal for a WordPress RCE is the web server process doing things a web server process should never do.

SIGMA Rules

YAML
---
title: Web Server Process Spawning Shell or Command Interpreter
description: Detects the web server or PHP worker process spawning command interpreters or download utilities, consistent with post-exploitation of a web application RCE such as a vulnerable WordPress plugin. PHP code execution almost always manifests as a child process of apache2, nginx, php-fpm, or httpd.
references:
  - https://www.securityweek.com/unauthenticated-rce-flaws-could-expose-200000-wordpress-sites-to-takeover/
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/04/06
status: experimental
tags:
  - attack.execution
  - attack.t1059
  - attack.initial_access
  - attack.t1190
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentImage|endswith:
      - '/apache2'
      - '/httpd'
      - '/nginx'
      - '/php-fpm'
      - '/php-fpm8.1'
      - '/php-fpm8.2'
      - '/php-fpm8.3'
  selection_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/zsh'
      - '/curl'
      - '/wget'
      - '/python'
      - '/python3'
      - '/perl'
      - '/nc'
      - '/ncat'
      - '/netcat'
      - '/base64'
      - '/chmod'
  condition: selection_parent and selection_child
falsepositives:
  - WordPress plugins that legitimately shell out for image processing or backups (rare; validate per-host)
  - Server management panels executing maintenance tasks as the web user
level: high
---
title: PHP File Written to WordPress Uploads Directory
description: Detects creation of PHP files in wp-content/uploads or sibling web-accessible directories. The uploads directory should contain media assets only; PHP written there is a strong indicator of webshell deployment following plugin exploitation.
references:
  - https://www.securityweek.com/unauthenticated-rce-flaws-could-expose-200000-wordpress-sites-to-takeover/
  - https://attack.mitre.org/techniques/T1505/003/
author: Security Arsenal
date: 2026/04/06
status: experimental
tags:
  - attack.persistence
  - attack.t1505.003
logsource:
  category: file_event
  product: linux
detection:
  selection:
    TargetFilename|contains:
      - '/wp-content/uploads/'
      - '/wp-content/cache/'
      - '/wp-content/upgrade/'
      - '/wp-content/plugins/the-events-calendar/'
    TargetFilename|endswith:
      - '.php'
      - '.phtml'
      - '.php5'
      - '.php7'
      - '.phar'
  condition: selection
falsepositives:
  - Legitimate plugin updates writing PHP files to the plugin directory (correlate with update windows; uploads and cache directories have no legitimate PHP writes)
level: high
---
title: Suspicious Request Pattern Against WordPress AJAX or REST Endpoints
description: Detects HTTP requests to WordPress admin-ajax.php or plugin REST routes carrying markers of command injection or serialized payload abuse, consistent with attempted exploitation of unauthenticated plugin endpoints.
references:
  - https://www.securityweek.com/unauthenticated-rce-flaws-could-expose-200000-wordpress-sites-to-takeover/
  - https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/04/06
status: experimental
tags:
  - attack.initial_access
  - attack.t1190
logsource:
  category: webserver
detection:
  selection_uri:
    c-uri|contains:
      - 'admin-ajax.php'
      - '/wp-json/'
  selection_payload:
    c-uri|contains:
      - 'system('
      - 'exec('
      - 'passthru('
      - 'shell_exec('
      - 'base64_decode'
      - 'eval('
      - 'assert('
      - '%3Bcat%20'
      - ';id;'
      - '|id'
      - 'whoami'
      - '/etc/passwd'
  condition: all of selection_*
falsepositives:
  - Extremely rare in legitimate traffic; security scanners and WAF probes may match and should be triaged rather than suppressed
level: high

KQL Hunt (Microsoft Sentinel / Defender)

This query assumes your WordPress hosts forward Syslog (auth/process) and web access logs into Sentinel via a Linux agent or CEF connector, and that any Windows-hosted instances are covered by Defender for Endpoint. It pivots on the two most reliable post-exploitation signals: web-process child shells and webshell writes.

KQL — Microsoft Sentinel / Defender
// Hunt 1: Web server / PHP-FPM spawning command interpreters (post-exploitation)
union isfuzzy=true
    (Syslog
    | where Facility == "auth" or ProcessName in~ ("apache2", "httpd", "nginx", "php-fpm")
    | where SyslogMessage has_any ("/bin/sh", "/bin/bash", "curl ", "wget ", "whoami", "/etc/passwd", "base64 -d")
    | extend Indicator = SyslogMessage, Source = "Syslog"
    | project TimeGenerated, Computer, ProcessName, Indicator, Source),
    (DeviceProcessEvents
    | where InitiatingProcessFileName has_any ("w3wp.exe", "php-cgi.exe", "httpd.exe", "nginx.exe")
    | where FileName in~ ("cmd.exe", "powershell.exe", "pwsh.exe", "sh", "bash", "curl.exe", "wget.exe")
    | extend Indicator = ProcessCommandLine, Source = "Defender"
    | project TimeGenerated, DeviceName, InitiatingProcessFileName, Indicator, Source)
| order by TimeGenerated desc
;

// Hunt 2: Suspicious requests hitting WordPress AJAX/REST endpoints (proxy/firewall/WAF logs via CEF)
CommonSecurityLog
| where RequestURL has_any ("admin-ajax.php", "/wp-json/", "/wp-content/plugins/the-events-calendar/")
| where RequestURL has_any ("base64_decode", "eval(", "system(", "shell_exec", "whoami", "/etc/passwd", ";id", "|id")
    or AdditionalExtensions has_any ("base64_decode", "eval(", "system(", "shell_exec")
| summarize Hits = count(), DistinctSources = dcount(SourceIP) by RequestURL, DestinationHostName, bin(TimeGenerated, 1h)
| order by Hits desc
;

// Hunt 3: PHP files appearing in uploads directories (file integrity data via Syslog/auditd)
Syslog
| where SyslogMessage has "wp-content/uploads" and SyslogMessage has_any (".php", ".phtml", ".phar")
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| order by TimeGenerated desc

Velociraptor VQL Hunt

Run this across your WordPress fleet to surface candidate webshells in web-accessible directories and anomalous web-server child processes. The 14-day lookback covers the likely disclosure-to-exploitation window; widen it if patching was delayed.

VQL — Velociraptor
-- Find recently written PHP files in WordPress web-accessible directories (webshell candidates)
LET webshells = SELECT FullPath, Size, Mtime, Ctime
FROM glob(globs=['/var/www/**/wp-content/uploads/**/*.php',
                 '/var/www/**/wp-content/cache/**/*.php',
                 '/srv/www/**/wp-content/uploads/**/*.php',
                 '/home/**/public_html/wp-content/uploads/**/*.php'])
WHERE Mtime > now() - 14 * 24 * 3600
ORDER BY Mtime DESC

-- Find web server processes with shell children (live post-exploitation)
LET shell_children = SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE (CommandLine =~ '/bin/(ba)?sh|curl |wget |nc |ncat |whoami|base64'
   OR Exe =~ '/(sh|bash|dash|nc|ncat)$')

SELECT * FROM webshells
UNION ALL
SELECT FullPath=NULL, Size=NULL, Mtime=CreateTime, Ctime=NULL FROM shell_children

Verification and Triage Script

Use this on Linux-hosted WordPress servers to confirm the plugin's presence and version, sweep for webshell indicators, and identify recently tampered files. Run it before and after patching — before, to establish whether you were already hit; after, to confirm the environment is clean.

Bash / Shell
#!/bin/bash
# The Events Calendar RCE - WordPress triage and verification script
# Run as root or a user with read access to the web root

echo "=== [1] Locate WordPress installations and check for The Events Calendar ==="
find /var/www /srv/www /home -maxdepth 6 -type d -name "the-events-calendar" 2>/dev/null

echo "=== [2] Report installed plugin version (from plugin header) ==="
find /var/www /srv/www /home -maxdepth 6 -type d -name "the-events-calendar" 2>/dev/null | while read -r plugindir; do
    echo "--- $plugindir ---"
    grep -m1 -i "^Version:" "$plugindir/the-events-calendar.php" 2>/dev/null || echo "Version header not found"
done

echo "=== [3] WP-CLI check (if available) — authoritative version and update status ==="
command -v wp >/dev/null 2>&1 && \
  find /var/www /srv/www /home -maxdepth 4 -name "wp-config.php" 2>/dev/null | while read -r cfg; do
    sitedir=$(dirname "$cfg")
    echo "--- $sitedir ---"
    sudo -u www-data wp --path="$sitedir" plugin list --format=table 2>/dev/null | grep -i "events-calendar"
  done || echo "wp-cli not installed; rely on header check above"

echo "=== [4] Webshell sweep: PHP files in uploads/cache directories ==="
find /var/www /srv/www /home -maxdepth 8 \
  \( -path "*/wp-content/uploads/*" -o -path "*/wp-content/cache/*" \) \
  -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.phar" -o -name "*.php*" \) 2>/dev/null

echo "=== [5] Recently modified PHP files across web roots (last 14 days) ==="
find /var/www /srv/www /home -maxdepth 8 -type f -name "*.php" -mtime -14 2>/dev/null | head -100

echo "=== [6] Web server spawning shells — last 5000 syslog/auth events ==="
grep -E "(apache2|httpd|nginx|php-fpm)" /var/log/syslog /var/log/auth.log 2>/dev/null | \
  grep -E "(/bin/sh|/bin/bash|curl |wget |whoami|/etc/passwd|base64 -d)" | tail -50

echo "=== [7] Suspicious requests in web access logs targeting the plugin ==="
grep -rEh "the-events-calendar|admin-ajax.php|wp-json" /var/log/apache2/*access*.log /var/log/nginx/*access*.log 2>/dev/null | \
  grep -Ei "base64_decode|eval\(|system\(|shell_exec|whoami|/etc/passwd|;id|id;" | tail -50

echo "=== [8] Rogue admin users (WP-CLI) ==="
command -v wp >/dev/null 2>&1 && \
  find /var/www /srv/www /home -maxdepth 4 -name "wp-config.php" 2>/dev/null | while read -r cfg; do
    sitedir=$(dirname "$cfg")
    echo "--- $sitedir ---"
    sudo -u www-data wp --path="$sitedir" user list --role=administrator --format=table 2>/dev/null
  done

echo "=== Triage complete. Review sections 4-8 for signs of prior compromise before patching. ==="

Critical operational note: if sections 4–8 surface anything suspicious, do not simply patch and move on. Patching over an existing webshell leaves the attacker inside. Isolate the host, preserve the web root and logs for forensic review, and treat it as an incident — rotate the database credentials from wp-config.php, all WordPress salts/keys, and any credentials stored on or reachable from that host.

Remediation

  1. Update The Events Calendar immediately on every WordPress instance in your estate — production, staging, and decommissioned-but-still-live sites. Apply the latest release from the official WordPress plugin repository or the vendor's (StellarWP / The Events Calendar) site, and confirm the installed version against the vendor's security advisory and changelog. If auto-updates are disabled for plugins, enable them for security releases at minimum.
  2. Inventory before you patch. You cannot patch what you have not catalogued. Query your CMS/asset management, run the discovery portions of the script above across hosting infrastructure, and check with marketing and business units that commission their own WordPress sites. Shadow WordPress is where these flaws live longest.
  3. If you cannot patch within hours, reduce exposure: temporarily deactivate the plugin, or place the site behind a WAF rule blocking unauthenticated requests to the plugin's endpoints. A virtual patch is a bridge, not a destination — scheduled it for removal once the update lands.
  4. Hunt before you trust. Because the flaw is unauthenticated and internet-facing, any host that was running a vulnerable version must be assessed for compromise, not just updated. Deploy the SIGMA rules above to your Linux web server estate, run the KQL hunts in Sentinel, and sweep uploads directories for rogue PHP. Webshell deployment is the standard follow-on action; check for it explicitly.
  5. Harden the platform against the next one: disable PHP execution in wp-content/uploads/ via web server configuration (deny execution of .php in that path), enforce least-privilege filesystem permissions on the web root, and ensure the WordPress database account has only the privileges it needs. These controls materially degrade webshell effectiveness even when the next plugin RCE lands.
  6. Rotate secrets on any suspect host: database credentials, WordPress authentication salts (wp-config.php), API keys stored in the database, and any service accounts the web user can read. Assume anything the PHP process could read is compromised.
  7. Formalize plugin governance. WordPress risk is plugin risk. Maintain an approved plugin list, remove anything not actively maintained, and fold WordPress estate patching into your vulnerability management SLA — unauthenticated RCE in a six-figure-install plugin belongs in your 24–72 hour emergency patch class, alongside your KEV-driven items.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.