A booby-trapped diary entry that only explodes if someone photocopies the whole filing cabinet
CVE-2026-93953 is a second-order SQL injection in the Backuply plugin for WordPress (≤ 1.5.8, fixed in 1.5.9). An attacker with at least subscriber-level access plants a crafted SQL payload in their WordPress profile's *Biographical Info* field. Backuply exports this value verbatim — unescaped — into the wp_usermeta INSERT statement of the SQL dump it generates. During a subsequent backup restore, the plugin's regex-based table-prefix rewriting logic (/CREATE\s+TABLE\s+…options…/is) matches the attacker-controlled text inside the biography, concatenates it directly into an UPDATE query, and the injected SQL executes with full database privileges. The payload typically flips users_can_register to 1 and sets default_role to administrator in wp_options, letting the attacker register a new admin account via the standard WordPress form.
The vendor's HIGH / 8.0 rating is overweight for real-world conditions. CVSS accounts for needing authentication (PR:L) and user interaction (UI:R), but it cannot model the magnitude of the interaction: the 'user interaction' here is not clicking a link — it is an administrator performing a full database restore, an event that occurs once a year on active sites and never on most. The attacker has zero control over whether or when the restore happens. That makes this a time-bomb with no detonator, which fundamentally changes the risk calculus. On an install base of 600,000 sites, only a thin slice will restore a backup during the window between payload insertion and patching. No public PoC exists, EPSS is 0.34%, and there is no observed exploitation. The severity lands closer to MEDIUM.
5 steps from start to impact.
Obtain subscriber account
- Target site allows public user registration, or attacker already has subscriber credentials
- Many enterprise/business WordPress sites disable public registration
- Sites behind SSO/IdP may not expose wp-login.php registration at all
Plant SQL payload in Biographical Info
wp_usermeta — the injection is dormant at this stage. No weaponized tooling is publicly available for this step as of October 2026.- Subscriber can edit their own profile (default WordPress behavior)
- Payload must be precisely crafted to match the regex pattern — requires understanding the plugin's restore code
- No public PoC or automated tool exists to generate the payload
description fields for SQL keywords would catch most payloads.Backuply creates a backup containing the poisoned record
wp_usermeta rows — including the attacker's biography — verbatim into the SQL file. The payload is now embedded in the backup archive. No additional attacker action is required.- Backuply ≤ 1.5.8 is installed and actively used for backups
- A backup is taken *after* the payload was planted
- If the site uses a different backup plugin (UpdraftPlus, BlogVault, VaultPress, etc.), this path is dead
- Backup frequency varies wildly — some sites back up daily, others monthly or manually
Administrator restores the poisoned backup
wp_options to enable open registration and set the default role to administrator.- An administrator must voluntarily initiate a full database restore
- The restore must use the poisoned backup (not an older clean one)
- This is the critical friction point. Database restores are rare events — disaster recovery, site migration, or staging refresh. Most production WordPress sites go months or years without a restore.
- The attacker has zero control over whether or when this happens
- If the admin restores from a backup taken *before* the payload was planted, the chain breaks
Attacker registers an administrator account
users_can_register flipped to 1 and default_role set to administrator, the attacker visits /wp-login.php?action=register and creates a new account that is automatically granted full administrator privileges. From here, the attacker has complete control of the WordPress site — plugin/theme editing, file upload, database access, and content manipulation.- The restore in step 4 completed successfully with the poisoned payload
- The attacker checks back (or monitors) to discover that registration is now open
- The attacker must poll the target to detect when the restore happened — there's no callback mechanism
- Security plugins may alert admins to the changed settings before the attacker exploits them
wp plugin update backuply) or wp-admin auto-update. Under noisgate's MEDIUM remediation SLA, this should be completed within 365 days — but given the simplicity of a WordPress plugin update, aim for days, not months.Settings → General → Anyone can register to unchecked. This eliminates the trivial subscriber-registration path (step 1), requiring the attacker to already have credentials. Dramatically narrows the attacker population.SELECT user_id, meta_value FROM wp_usermeta WHERE meta_key = 'description' AND (meta_value LIKE '%UPDATE%wp_options%' OR meta_value LIKE '%default_role%' OR meta_value LIKE '%users_can_register%'); Any matches are suspicious and should be sanitized. This detects planted payloads before they can fire during a future restore.grep -P 'UPDATE.*wp_options.*default_role' backup.sql. If your restore process includes this check, you catch the payload before it executes, breaking the chain at step 4.- Generic WAF SQL injection rules — The payload is injected via a standard WordPress profile update (POST to
/wp-admin/profile.php). The malicious SQL is stored in a free-text biography field that legitimately accepts arbitrary text. Generic SQLi signatures look for SQL in query parameters or headers, not in profile bio fields. Only Wordfence's CVE-specific virtual patch catches this. - Rate limiting or brute-force protection — This is a one-shot payload insertion, not a volume-based attack. Rate limiting has no effect.
- Content Security Policy or security headers — This is entirely a server-side SQL injection triggered during PHP execution of a restore operation. No client-side defenses are relevant.
- Disabling REST API or XML-RPC — The payload is planted via the standard wp-admin profile editor, not through API endpoints.
The supporting signals.
| In-the-wild exploitation | None observed. Not listed in CISA KEV. No campaigns reported by Wordfence, Sucuri, Patchstack, or WPScan as of October 20, 2026. |
|---|---|
| Proof-of-concept availability | No public PoC. Not indexed on pocindex.io. No CVE-specific GitHub repos found. The SecureWithUmer/CVE-2026-PoCs collection does not include this CVE. Exploitation requires understanding Backuply's internal regex and crafting a precise payload — non-trivial without source review. |
| EPSS score | 0.0034 (0.34%) — bottom quintile. FIRST's model rates this as very unlikely to be exploited in the next 30 days, consistent with the second-order trigger requirement. |
| CISA KEV status | Not listed. No federal agency mandate to patch. |
| CVSS vector interpretation | CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H — Network-reachable, low complexity, requires low privileges and user interaction, unchanged scope, full CIA impact. The UI:R underweights the actual interaction burden (admin-initiated restore vs. a simple click). |
| Affected versions | Backuply ≤ 1.5.8 (all versions). Plugin is developed by Softaculous. |
| Fixed version | Backuply 1.5.9 (released October 1, 2026). No distro backports applicable — WordPress plugins update via wp-admin or WP-CLI. |
| Installed base / exposure | 600,000+ active WordPress installations per wordpress.org. Sites are web-facing by nature, but the vulnerability requires authenticated access *and* an admin-triggered restore, so internet exposure alone is not sufficient for exploitation. |
| Disclosure timeline | Discovered September 17, 2026 by researcher daroo ($1,352 Wordfence bounty). Reported to vendor September 28, 2026. Patch released October 1, 2026. Public disclosure October 15, 2026. |
| Wordfence virtual patch | Premium/Care/Response customers protected since September 24, 2026 (before public disclosure). Free tier coverage begins October 24, 2026. |
Sources.
Why this verdict
- Second-order trigger is a deal-breaker for mass exploitation. The attacker cannot fire the payload on demand. It only executes when an administrator voluntarily restores a database backup — an event that is infrequent by nature (disaster recovery, migration). CVSS's UI:R treats this the same as clicking a link; real-world friction is orders of magnitude higher.
- Zero attacker control over timing. The payload sits dormant in the database indefinitely. The attacker must poll the target to detect if/when a restore occurred. Most WordPress sites never restore backups during the realistic exploitation window (disclosure → patch adoption).
- No public PoC and no observed exploitation. EPSS at 0.34% confirms the model's read that this is unlikely to see broad exploitation. Crafting a working payload requires reverse-engineering Backuply's internal regex — non-trivial without a published writeup.
- Subscriber auth is low-to-moderate friction. On sites with open registration, trivial. On enterprise/gated sites, requires prior credential compromise. This prevents a full downgrade to LOW but is not sufficient to maintain HIGH.
- Role multiplier: WordPress sites running Backuply occupy the *web application tier* — not identity, hypervisor, CI/CD, backup infrastructure, or network edge in the enterprise sense. (a) *Low-value role:* dev/staging sites — chain succeeds if restore is triggered, blast radius is a throwaway host. (b) *Typical role:* small business / marketing site — chain succeeds, blast radius is a single website (host-level), possible PII exposure if e-commerce. (c) *High-value role:* WooCommerce stores with payment/PII data — chain succeeds, blast radius is still single-site. Even the worst outcome is host-scoped, not domain/fleet/supply-chain. Backuply is not canonically a high-value-role component per the catalog — it's a WordPress plugin, and no WordPress plugin constitutes an identity provider, domain controller, hypervisor, or network edge. The floor rule does not elevate this beyond MEDIUM.
- 600K installs provides breadth but the second-order trigger throttles actual exploitation. Even if 10% of sites have open registration and the attacker plants payloads, only the fraction that restores a poisoned backup within the exploitation window is at risk — realistically <1% of the install base.
Why not higher?
The vendor's HIGH/8.0 assumes the UI:R interaction is a garden-variety click. In reality, the required interaction is a full database restore — an operational event that is rare, deliberate, and often preceded by integrity checks. The attacker has zero agency in triggering it. With no public PoC, no exploitation, and an EPSS of 0.34%, there is no evidence to support maintaining HIGH. The blast radius is single-site, not infrastructure-tier.
Why not lower?
Subscriber-level auth is genuinely easy to obtain on the large fraction of WordPress sites that allow open registration. The payload, once planted, is persistent and invisible without targeted auditing. If a restore does occur, the impact is full site takeover with admin-level database access — which on e-commerce or membership sites can mean PII and payment data exposure. The 600K install base means the aggregate population at risk is non-trivial even at a low per-site exploitation rate.
Crowdsourced verification payload.
Run this on the WordPress host as a user with read access to the WordPress installation directory (typically www-data or via sudo). Invoke: bash check_backuply_cve2026_93953.sh /var/www/html (pass the WordPress root path as the first argument). No network access or database credentials required — it checks the plugin version on disk.
#!/usr/bin/env bash
# check_backuply_cve2026_93953.sh
# Checks whether the installed Backuply plugin is vulnerable to CVE-2026-93953
# Usage: bash check_backuply_cve2026_93953.sh /path/to/wordpress
# Exit codes: 1 = VULNERABLE, 0 = PATCHED, 2 = UNKNOWN
set -euo pipefail
WP_ROOT="${1:?Usage: $0 /path/to/wordpress}"
PLUGIN_FILE="${WP_ROOT}/wp-content/plugins/backuply/backuply.php"
if [ ! -f "$PLUGIN_FILE" ]; then
echo "UNKNOWN — Backuply plugin not found at ${PLUGIN_FILE}"
echo "The plugin may not be installed, or the WordPress path is incorrect."
exit 2
fi
# Extract the Version: header from the plugin's main file
VERSION=$(grep -iP '^\s*\*?\s*Version:\s*' "$PLUGIN_FILE" | head -1 | sed -E 's/^.*Version:\s*([0-9.]+).*/\1/')
if [ -z "$VERSION" ]; then
echo "UNKNOWN — Could not parse Backuply version from ${PLUGIN_FILE}"
exit 2
fi
echo "Detected Backuply version: ${VERSION}"
# Compare versions: vulnerable <= 1.5.8, patched >= 1.5.9
# Use sort -V for version comparison
MIN_SAFE="1.5.9"
LOWEST=$(printf '%s\n%s' "$VERSION" "$MIN_SAFE" | sort -V | head -1)
if [ "$VERSION" = "$MIN_SAFE" ] || [ "$LOWEST" = "$MIN_SAFE" ]; then
echo "PATCHED — Backuply ${VERSION} is >= ${MIN_SAFE}. CVE-2026-93953 is fixed."
exit 0
else
echo "VULNERABLE — Backuply ${VERSION} is < ${MIN_SAFE}. Affected by CVE-2026-93953."
echo "Update to Backuply 1.5.9+ immediately: wp plugin update backuply"
exit 1
fi