Back to Intelligence

CVE-2026-109218: Critical WordPress Contact Manager Arbitrary File Upload (CVSS 9.8) — Detection and Remediation Guide

SA
Security Arsenal Team
October 28, 2026
11 min read

NVD has published CVE-2026-109218, a CVSS 9.8 (Critical) vulnerability affecting the Contact Manager plugin for WordPress, in all versions up to and including 9.1.2. The flaw is an unauthenticated arbitrary file upload in the plugin's add_message function, and it is remotely exploitable over the network with no user interaction and no credentials required. In practical terms: any internet-facing WordPress site running a vulnerable version of this plugin can be turned into a fully compromised web server by an attacker who sends a single crafted HTTP request.

The vulnerability is a textbook example of why blacklist-based input validation fails. The plugin attempts to block dangerous file extensions, but its extension-derivation routine returns an empty string when the filename ends in a trailing dot (e.g., shell.php.). WordPress's sanitize_file_name() then silently strips the trailing dot before the file hits disk — meaning a file that the blacklist evaluated as "extensionless" is ultimately written as shell.php. If the uploads directory is reachable by the web server and PHP execution is not disabled there, the attacker now has unauthenticated remote code execution and, typically, a webshell.

If you run WordPress with this plugin, treat this as an emergency patching event. If you are an MSSP client or SOC operator, treat every vulnerable instance as potentially already compromised until proven otherwise — unauthenticated upload-to-RCE bugs are among the fastest to be weaponized once published.

Technical Analysis

Affected Products and Versions

ItemDetail
ComponentContact Manager plugin for WordPress
Affected versionsAll versions ≤ 9.1.2
CVECVE-2026-109218
CVSS v3.19.8 (Critical) — Network, Low complexity, No privileges, No user interaction
Vulnerability typeCWE-434 (Unrestricted Upload of File with Dangerous Type) via incomplete blacklist
Referencehttps://nvd.nist.gov/vuln/detail/CVE-2026-109218

Root Cause: The Trailing-Dot Bypass

The vulnerable code path lives in the plugin's add_message function, which processes form submissions that can include file attachments. The defensive logic works roughly like this:

  1. The plugin extracts the file extension from the user-supplied filename using a derivation routine (typically splitting on the last .).
  2. It checks that extension against a blacklist of dangerous extensions (.php, .phtml, .phar, etc.).
  3. It passes the filename through WordPress's sanitize_file_name().
  4. It writes the file to a web-accessible uploads directory.

The failure is in step 1 interacting with step 3. When the attacker submits a filename like payload.php.:

  • The extension-derivation routine sees the trailing dot and returns an empty string for the extension.
  • An empty string is not on the blacklist, so the file passes validation.
  • sanitize_file_name() removes the trailing dot, normalizing the name to payload.php.
  • The file is written to disk as executable PHP.

This is the classic parse-then-normalize ordering flaw: security validation is performed on a representation of the input that differs from what is actually persisted. It is the same class of mistake behind countless upload bypasses, and it is exactly why the industry guidance (including OWASP and WordPress's own hardening documentation) recommends whitelist-based extension validation plus server-side content inspection, never blacklists.

Exploitation Chain (Defender's View)

  1. Attacker identifies a WordPress site running Contact Manager ≤ 9.1.2 (plugin fingerprinting via asset paths, readme.txt, or scanner tooling).
  2. Attacker sends an unauthenticated POST to the plugin's message-submission endpoint with a multipart attachment named shell.php. (or variants using .phtml., .php5., .phar.).
  3. The bypassed blacklist allows the upload; sanitize_file_name() strips the trailing dot; the file lands in a web-accessible path (commonly under wp-content/uploads/ or a plugin-specific subdirectory).
  4. Attacker requests the uploaded file directly over HTTP. If PHP execution is enabled in that directory — the default on a large share of Apache + mod_php and many nginx/php-fpm deployments — the code executes with web-server privileges.
  5. Post-exploitation typically follows quickly: webshell deployment (WSO, b374k variants, or simple system($_GET[...]) one-liners), additional payload staging, database credential theft from wp-config.php, and lateral movement or SEO spam/malware distribution from the compromised host.

Exploitation Status

At publication, the NVD entry is fresh and CISA KEV inclusion should be monitored closely. Historically, unauthenticated WordPress plugin file-upload-to-RCE vulnerabilities transition from disclosure to mass scanning and exploitation within 24–72 hours — WordPress's enormous installed base makes these bugs among the most aggressively automated targets on the internet. Given the CVSS 9.8 score, the unauthenticated attack path, and the simplicity of the trailing-dot bypass (no race conditions, no authentication, no special conditions), defenders should assume active exploitation is imminent or already occurring and operate accordingly: patch immediately, hunt retroactively for uploads, and check for webshells.

Detection & Response

This is a technical threat, and the detections below are built around the specific observable artifacts this attack produces: HTTP POSTs to the plugin endpoint, files with trailing-dot or double-extension patterns in uploads directories, PHP files appearing in content/upload trees, and web server processes spawning shells.

Sigma Rules

YAML
---
title: PHP File Written to WordPress Uploads Directory
id: 3f9a1c54-7b2e-4d81-9a6f-0c5e8d2b4a71
status: experimental
description: Detects creation of PHP or script files inside WordPress uploads/content directories, a primary artifact of CVE-2026-109218 arbitrary file upload exploitation.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-109218
  - https://attack.mitre.org/techniques/T1505/003/
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.persistence
  - attack.t1505.003
logsource:
  category: file_event
  product: linux
detection:
  selection_path:
    TargetFilename|contains:
      - '/wp-content/uploads/'
      - '/wp-content/plugins/contact-manager/'
      - '/wp-content/upgrade/'
  selection_ext:
    TargetFilename|endswith:
      - '.php'
      - '.phtml'
      - '.phar'
      - '.php5'
      - '.php7'
      - '.php.'
      - '.phtml.'
      - '.phar.'
  condition: selection_path and selection_ext
falsepositives:
  - Legitimate plugin/theme updates writing PHP files (rare in uploads; common under wp-content/plugins core paths — tune by excluding known update tooling)
  - Developer deployments via CI/CD
level: high
---
title: Web Server Process Spawning Shell or System Utilities
id: 8c1d2e47-6a3b-4f92-b5d8-1e7a9c3f5b62
status: experimental
description: Detects Apache, nginx, or PHP-FPM worker processes spawning shells or system utilities, consistent with post-exploitation after webshell deployment via CVE-2026-109218.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-109218
  - https://attack.mitre.org/techniques/T1059/004/
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.execution
  - attack.t1059.004
  - attack.t1505.003
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentImage|endswith:
      - '/apache2'
      - '/httpd'
      - '/nginx'
      - '/php-fpm'
      - '/php'
  selection_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/zsh'
      - '/curl'
      - '/wget'
      - '/nc'
      - '/ncat'
      - '/python'
      - '/python3'
      - '/perl'
      - '/base64'
  condition: selection_parent and selection_child
falsepositives:
  - Some backup/migration plugins legitimately shell out (e.g., to tar/mysqldump) — tune by parent command line and scheduled task windows
level: high
---
title: HTTP Upload with Trailing-Dot or Double-Extension Filename
id: 5b7e3f18-2d4c-4a85-8e91-6f2c0a7d3b45
status: experimental
description: Detects web access log entries showing multipart POSTs to WordPress plugin endpoints or direct requests for uploaded files with trailing-dot or double extensions, indicative of CVE-2026-109218 exploitation attempts.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-109218
author: Security Arsenal
date: 2026/04/06
tags:
  - attack.initial_access
  - attack.t1190
logsource:
  category: webserver
detection:
  selection_uri_uploads:
    c-uri|contains:
      - '/wp-content/uploads/'
  selection_uri_ext:
    c-uri|contains:
      - '.php.'
      - '.phtml.'
      - '.phar.'
      - '.php%2e'
      - '.php/'
  selection_plugin:
    c-uri|contains:
      - 'contact-manager'
      - 'add_message'
  condition: (selection_uri_uploads and selection_uri_ext) or selection_plugin
falsepositives:
  - Legitimate use of the Contact Manager form (selection_plugin branch) — alert on conjunction with upload POST body size and subsequent webshell requests; treat plugin-endpoint hits from unknown external IPs as medium severity context for correlation
level: medium

KQL — Microsoft Sentinel / Defender

WordPress hosts typically log into Sentinel via Syslog/CEF ingestion of Apache/nginx access logs, or via Defender for Endpoint on the web server. The query below hunts for the two strongest signals: script files appearing in uploads paths, and web server processes spawning shells.

KQL — Microsoft Sentinel / Defender
// Hunt 1: Script/PHP file creation under WordPress uploads (Defender for Endpoint on web server)
DeviceFileEvents
| where TimeGenerated > ago(14d)
| where FolderPath has_any ("wp-content/uploads", "wp-content\\uploads", "contact-manager")
| where FileName endswith ".php" or FileName endswith ".phtml" or FileName endswith ".phar"
   or FileName endswith ".php5" or FileName endswith ".php7"
| project TimeGenerated, DeviceName, FolderPath, FileName, InitiatingProcessAccountName, InitiatingProcessFileName, SHA256
| order by TimeGenerated desc
;
// Hunt 2: Web server spawning shells/utilities (post-exploitation via uploaded webshell)
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where InitiatingProcessFileName in~ ("apache2", "httpd", "nginx", "php-fpm", "php", "w3wp.exe")
| where FileName in~ ("sh", "bash", "dash", "curl", "wget", "nc", "ncat", "python3", "perl", "base64", "cmd.exe", "powershell.exe")
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by TimeGenerated desc
;
// Hunt 3: Access-log evidence (Apache/nginx logs ingested via Syslog/CEF) — trailing-dot or plugin endpoint hits
CommonSecurityLog
| where TimeGenerated > ago(14d)
| where RequestURL has_any (".php.", ".phtml.", ".phar.", "contact-manager", "add_message")
   or (RequestURL has "wp-content/uploads" and RequestURL has_any (".php", ".phtml", ".phar") and RequestMethod == "GET")
| summarize Hits = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, RequestURL, RequestMethod, RequestContext
| order by LastSeen desc

Velociraptor VQL

Use this hunt artifact to sweep web servers for recently created executable files in WordPress content trees — the highest-fidelity artifact of this vulnerability being exploited.

VQL — Velociraptor
-- Hunt for recently created PHP/script files in WordPress uploads directories
-- indicative of CVE-2026-109218 arbitrary file upload exploitation
SELECT
  FullPath AS FilePath,
  Size AS FileSize,
  Mtime AS ModifiedTime,
  Btime AS CreatedTime
FROM glob(
  globs=[
    '/var/www/**/wp-content/uploads/**/*.php',
    '/var/www/**/wp-content/uploads/**/*.phtml',
    '/var/www/**/wp-content/uploads/**/*.phar',
    '/var/www/**/wp-content/uploads/**/*.php5',
    '/srv/www/**/wp-content/uploads/**/*.php',
    '/home/**/public_html/wp-content/uploads/**/*.php'
  ]
)
WHERE Mtime > Now() - 2592000  -- files modified in the last 30 days
ORDER BY ModifiedTime DESC

Follow up any hit by collecting the file (upload() the matched path) and checking contents for classic webshell markers: eval(, base64_decode(, gzinflate(, shell_exec(, system($_, assert(, passthru(, and $_REQUEST/$_POST/$_GET execution sinks.

Remediation / Hardening Script (Bash)

Run this on Linux-hosted WordPress servers to (1) identify the vulnerable plugin version, (2) block PHP execution in uploads directories as an immediate compensating control, and (3) sweep for suspicious files.

Bash / Shell
#!/bin/bash
# CVE-2026-109218 — Contact Manager <= 9.1.2 mitigation & triage script
# Run as root on the WordPress host. Adjust WP_PATH to your document root.

WP_PATH="/var/www/html"

echo "[*] Locating WordPress installations and Contact Manager versions..."
for PLUGIN_DIR in $(find / -type d -path "*wp-content/plugins/contact-manager" 2>/dev/null); do
    echo "    Found: $PLUGIN_DIR"
    grep -h "Version:" "$PLUGIN_DIR"/*.php 2>/dev/null | head -1
done

echo "[*] If wp-cli is available, check version precisely:"
# sudo -u www-data wp --path=$WP_PATH plugin list | grep contact-manager

echo "[*] Applying compensating control: deny PHP execution in uploads (Apache)"
HTACCESS="$WP_PATH/wp-content/uploads/.htaccess"
cat > "$HTACCESS" <<'EOF'
# Block execution of uploaded scripts (CVE-2026-109218 mitigation)
<FilesMatch "\.(php|phtml|phar|php3|php4|php5|php7|pht|pl|py|cgi|asp|aspx)$">
    Require all denied
</FilesMatch>
php_flag engine off
EOF
echo "    Written: $HTACCESS"

echo "[*] For nginx hosts, add to the server block instead:"
cat <<'EOF'
    location ~* /wp-content/uploads/.*\.(php|phtml|phar|php[0-9]|pht)$ {
        deny all;
    }
EOF

echo "[*] Sweeping uploads for script files (potential webshells)..."
find "$WP_PATH/wp-content/uploads" -type f \( -name "*.php*" -o -name "*.phtml" -o -name "*.phar" \) -mtime -60 -ls 2>/dev/null

echo "[*] Sweeping for trailing-dot / double-extension upload artifacts..."
find "$WP_PATH/wp-content/uploads" -type f -name "*." 2>/dev/null
find "$WP_PATH/wp-content/uploads" -type f -regextype posix-extended -regex ".*\.(php|phtml|phar)\..*" 2>/dev/null

echo "[*] Grep uploads tree for common webshell markers..."
grep -rIl --include="*.php" -E "(eval\(|base64_decode\(|gzinflate\(|shell_exec\(|passthru\(|assert\(|system\(\$_)" "$WP_PATH/wp-content/uploads" 2>/dev/null

echo "[*] Triage complete. Any file returned above MUST be collected for analysis before deletion."
echo "[*] Patch: update Contact Manager to a fixed version > 9.1.2 or remove the plugin entirely."

Remediation

  1. Patch immediately. Update the Contact Manager plugin to the fixed release beyond 9.1.2 as soon as the vendor publishes it. Via wp-cli: wp plugin update contact-manager. If no fixed release is available at the time you read this, deactivate and remove the plugin — there is no acceptable workaround that keeps the vulnerable code path reachable.
  2. Block PHP execution in uploads directories as a permanent defense-in-depth measure (see the .htaccess and nginx configurations in the script above). This is a compensating control, not a fix — but it neutralizes the entire class of upload-to-RCE bugs and should be standard on every WordPress deployment you operate.
  3. Hunt retroactively before you assume you're clean. Because exploitation of unauthenticated upload bugs is typically automated within hours of disclosure, any site that was running ≤ 9.1.2 after the CVE publication date should be swept for webshells (VQL/KQL above), unexpected admin users (wp user list --role=administrator), modified core files (wp core verify-checksums), and rogue scheduled tasks/cron entries.
  4. Assume credential exposure on confirmed compromise. wp-config.php contains database credentials in cleartext, and webshell access means those are burned. Rotate database passwords, WordPress salts/keys, any API keys in plugin settings, and any credentials reused elsewhere.
  5. Reduce future exposure. Deploy a WAF or plugin-level virtual patching (e.g., managed rulesets that block multipart uploads with executable extensions and trailing-dot patterns), enforce auto-updates for WordPress plugins, and run a continuous attack-surface inventory so you know within minutes — not weeks — which of your properties run a newly vulnerable plugin.
  6. Monitor authoritative sources. Track the NVD entry (https://nvd.nist.gov/vuln/detail/CVE-2026-109218), the WordPress plugin repository changelog for Contact Manager, and CISA KEV for confirmation of active exploitation. If KEV-listed, CISA's remediation deadline applies to federal civilian agencies and should be treated as your deadline too.

The defensive lesson here extends past one plugin: blacklist validation on attacker-controlled filenames will always eventually fail. When you review code or assess vendors, look for validation that happens before normalization, blacklist (rather than whitelist) extension logic, and uploads written to web-executable paths. Each is a finding; together they are the next CVE-2026-109218.

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.