A password-reset flow that trusts the phone number the attacker just typed in
The SMS Alert – SMS & OTP for WooCommerce plugin (versions ≤ 3.9.5) exposes an unauthenticated endpoint in its OTP-based password reset flow that fails to validate the requester's identity before mutating the target user's profile. An attacker can submit a target username (including admin), overwrite the account's email address or phone number, then trigger an OTP reset to a channel they control — ending with a working admin login. The bug is a textbook CWE-287 (Improper Authentication) in the reset handler, not a subtle race or memory issue.
Vendor CRITICAL / 9.8 matches the vector on paper: network, no auth, no interaction, full CIA impact on a WordPress admin. But the vector ignores the plugin's own precondition — the vulnerable code path only fires on sites that have enabled OTP-based password reset AND have a phone number stored on the target account. That is a minority configuration even among installs of this specific plugin. Real-world severity is HIGH, not CRITICAL, but it is emphatically not something to defer.
5 steps from start to impact.
Enumerate WordPress sites running SMS Alert with OTP reset enabled
sms-alert plugin via /wp-content/plugins/sms-alert/ asset paths or the plugin's public JS handles. They then probe the login page for the OTP-reset UI element to confirm the feature toggle is on. Tools: wpscan, httpx with plugin-path templates, custom nuclei template.- Target site publicly reachable
- Plugin ≤ 3.9.5 installed
- OTP-based password reset feature enabled in plugin settings
- Feature is opt-in — most SMS Alert installs use it only for order notifications, not password reset
- Sites behind Cloudflare WAF managed rules may block the plugin fingerprint scan
Identify a target account with a stored phone number
admin (or usernames from /wp-json/wp/v2/users if REST enum is open) and reads the OTP-flow response to distinguish 'phone-on-file' from 'no-phone'. Custom Burp Intruder or a small Python harness works.- Valid admin/editor username known or enumerable
- Target user has a phone number field populated by the plugin
- Many WP hardening guides disable
/wp-json/wp/v2/usersenumeration - Small merchants using this plugin often only store phone numbers on *customer* accounts, not admins
admin-ajax.php action are visible in access logs; Wordfence rate-limits reset attempts.Overwrite the target user's email/phone via unauthenticated endpoint
user_login plus an attacker-controlled phone number or email. Because the handler skips ownership verification, the profile field is updated. This is the core CWE-287 flaw. PoC-grade curl is sufficient.- Reachable
wp-admin/admin-ajax.php - Knowledge of the vulnerable action name (public in plugin source)
- ModSecurity CRS rule 942xx sometimes trips on unusual POST bodies
- Some managed WP hosts (WP Engine, Kinsta) proxy
admin-ajax.phpwith per-action allow-lists
Trigger OTP delivery to attacker-controlled channel and complete reset
- SMS/email delivery quota not exhausted
- Site's WP login not restricted by IP allow-list or 2FA-at-cookie layer
- Sites layering a WP-level 2FA plugin (Wordfence 2FA, miniOrange) still require the second factor at login even after password reset
- SSO-only admin logins fully mitigate
Post-auth: install malicious plugin / webshell / skimmer
- Admin session established
- File edit or plugin install not disabled (
DISALLOW_FILE_MODS)
- Sites with
define('DISALLOW_FILE_MODS', true)block plugin uploads from the UI - Read-only container filesystems (managed WP hosting) blunt persistence
The supporting signals.
| In-the-wild status | No confirmed exploitation as of 2026-07-01. Not on CISA KEV. WordPress plugin auth-bypass bugs historically see mass exploitation within 1-2 weeks of public PoC. |
|---|---|
| Public PoC | None public at disclosure; Wordfence advisory withholds request details for a grace window. Expect a Patchstack/Wordfence write-up within days. |
| EPSS | Not yet scored on disclosure day. Comparable unauth WP privilege-escalation CVEs typically land in the 0.85+ / 97th percentile band within 30 days. |
| KEV status | Not listed. Watch CISA KEV over the next 60 days — WordPress plugin auth bypasses (e.g., WooCommerce Payments CVE-2023-28121) have been added retroactively when exploitation surged. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — accurate on the technical bug, but does not encode the *feature-must-be-enabled* precondition. |
| Affected versions | SMS Alert – SMS & OTP for WooCommerce ≤ 3.9.5 (all versions up to and including). |
| Fixed version | 3.9.6 (vendor patch, WordPress.org plugin repository). Auto-update eligible via WP core if enabled. |
| Installed base | Plugin reports ~10,000+ active installs on WordPress.org — mid-tier plugin, not top-100. Realistic exposed population is a subset (OTP-reset feature enabled). |
| Exposure data | PublicWWW / BuiltWith fingerprints ~8-12k sites carrying the plugin asset path. GreyNoise has no tag yet; Shodan http.component:"wordpress" http.html:"sms-alert" queries return low hundreds of confirmable hosts. |
| Disclosure & credit | Disclosed 2026-07-01 via Wordfence / Patchstack coordinated disclosure. Reporting researcher credited in the Wordfence Intelligence advisory. |
noisgate verdict.
Downgraded from CRITICAL to HIGH because the decisive factor is a narrow enablement precondition: the vulnerable code path only executes on installs that have turned on OTP-based password reset AND stored a phone number on the target account, which is a small fraction of the plugin's ~10k install base. When the chain does land, it is a full unauthenticated admin takeover of a WooCommerce store — cardholder-data blast radius keeps this firmly in HIGH, not MEDIUM.
Why this verdict
- Baseline is fair but overstated: vendor 9.8 correctly captures the auth-bypass class but ignores the plugin-side feature toggle that gates the vulnerable path.
- Friction — feature enablement: OTP-based *password reset* is a distinct setting from OTP for order notifications; most SMS Alert deployments use only the latter. Estimated <20% of installs are exposed.
- Friction — account precondition: target user must have a phone number on file. Many WP admins never populate that field, further narrowing to well below 10% of installs.
- Role multiplier: the affected component is a WooCommerce site — a payment-handling role. Chain success ends in admin takeover of a live e-commerce checkout, enabling Magecart skimmers and PII/PCI egress. Blast radius is per-site (tenant-scale), not fleet-scale, so the CRITICAL floor does not apply — but the HIGH floor does.
- No fleet-wide amplifier: this is not an IdP, hypervisor, CA, or kernel-mode agent. Compromise is one WordPress site per exploit; there is no lateral pivot into corporate identity unless the site is oddly integrated.
- Exploitation window is short: WP plugin auth-bypasses are commodity-weaponized fast. Absence of PoC today is not comfort — Patchstack/Wordfence signatures usually precede mass scanning by days, not weeks.
Why not higher?
Not CRITICAL because the vulnerable code path is gated by two opt-in conditions (OTP-reset feature enabled AND phone number on target account) that eliminate the majority of the installed base. There is no active exploitation, no KEV listing, and the affected component is not a canonical high-value-role system (IdP, hypervisor, CA, backup, kernel agent) that would force a CRITICAL floor.
Why not lower?
Not MEDIUM because when the chain does land it is fully unauthenticated remote admin takeover of a WooCommerce store — payment-card and PII exposure with regulatory (PCI-DSS, GDPR, state breach-notification) consequences. Historical priors on WooCommerce plugin auth bypasses show mass exploitation within days of PoC publication.
What to do — in priority order.
- Disable OTP-based password reset in the plugin settings — Turns off the vulnerable code path entirely without uninstalling. Fastest mitigation — apply within the noisgate mitigation SLA of 30 days for HIGH, ideally same-day on any WooCommerce site handling payments.
- Deploy Wordfence or Patchstack virtual patch / vPatch — Both vendors typically ship a firewall rule blocking the vulnerable AJAX action within 24-48h of disclosure. Enable auto-update for firewall rules and confirm the rule ID is active in your fleet.
- Restrict
wp-adminandadmin-ajax.phpat the edge to known IPs where feasible — For merchants with a small admin team, IP allow-list at Cloudflare/WAF layer blocks the attack outright. Not feasible for public checkout, butwp-adminrestriction is. - Enforce a WordPress-layer 2FA plugin on all admin accounts — Even if the attacker resets the password, a second factor tied to the admin's real device stops login. Wordfence 2FA, miniOrange, or Two-Factor plugin all work; enforce, don't just enable.
- Set
define('DISALLOW_FILE_MODS', true)inwp-config.php— Blocks admin-level plugin/theme uploads and code editing — turns a successful admin takeover into a much lower-impact event. Combine with read-only deploy pipeline.
- Changing admin usernames from
admin— the attacker can enumerate usernames via/wp-json/wp/v2/usersor author archive URLs. - Strong admin passwords — the flaw is a reset bypass; it does not need the current password.
- Generic ModSecurity CRS without a plugin-specific rule — the malicious POST looks like normal AJAX and rarely trips CRS scoring.
- WordPress core updates — this is a plugin bug; WP core patches will not fix it.
Crowdsourced verification payload.
Run this from an auditor workstation with curl and jq available. It probes a target WordPress site to detect the plugin version. Invoke as: ./check_cve_2026_11387.sh https://store.example.com. No credentials required.
#!/usr/bin/env bash
# noisgate check: CVE-2026-11387 — SMS Alert for WooCommerce ≤ 3.9.5
# Usage: ./check_cve_2026_11387.sh <site_url>
# Exit 0: PATCHED, 1: VULNERABLE, 2: UNKNOWN, 3: usage error
set -u
if [ $# -lt 1 ]; then
echo "Usage: $0 <site_url>" >&2
exit 3
fi
SITE="${1%/}"
FIXED="3.9.6"
PLUGIN_README="$SITE/wp-content/plugins/sms-alert/readme.txt"
# Fetch plugin readme
BODY=$(curl -sSL --max-time 15 -A "noisgate-check/1.0" "$PLUGIN_README" || true)
if [ -z "$BODY" ]; then
echo "UNKNOWN: could not retrieve $PLUGIN_README (plugin may not be installed, or path blocked)"
exit 2
fi
# Extract Stable tag
VER=$(printf '%s' "$BODY" | grep -iE '^Stable tag:' | head -1 | sed -E 's/.*:\s*//; s/[[:space:]]+$//')
if [ -z "$VER" ]; then
echo "UNKNOWN: readme.txt found but no Stable tag parsed"
exit 2
fi
# Semver-ish compare against 3.9.6
verlte() { [ "$1" = "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -1)" ]; }
if verlte "$VER" "3.9.5"; then
echo "VULNERABLE: sms-alert version $VER <= 3.9.5 (fix: $FIXED)"
echo "NOTE: exploitability also requires the OTP-based password reset feature enabled in plugin settings."
exit 1
else
echo "PATCHED: sms-alert version $VER >= $FIXED"
exit 0
fi
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.