Someone left a sticky note with every master key on the break-room fridge, and the break room is unlocked
CVE-2026-5076 targets the ARMember Premium plugin for WordPress (all versions through 7.3.1). When any user triggers a password reset, the plugin writes a plaintext copy of the 20-character reset key into the arm_reset_password_key field in wp_usermeta — right alongside the properly hashed key WordPress core stores in wp_users.user_activation_key. That plaintext key works with ARMember's custom armrp reset action to set a new password for any account, including administrators. Alone, the plaintext storage is a design flaw that needs another read primitive to weaponize. In practice, that primitive ships in the same plugin, same version range: CVE-2026-5073 provides unauthenticated SQL injection via the arm_directory_paging_action AJAX handler (registered on wp_ajax_nopriv_, no auth check). Chain them together and you get a zero-click, fully unauthenticated admin account takeover with two public PoCs automating every step.
Wordfence rated the chain CRITICAL 9.8 and the CVSS vector (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) is technically sound for the combined unauthenticated→admin-takeover path. However, 9.8 overweights the real-world footprint. ARMember Premium has roughly 30,000–50,000 active installations — a niche membership plugin, not a ubiquitous WordPress component like WooCommerce or Elementor. In a typical 10,000-host enterprise, the number of WordPress instances running ARMember is likely zero to low single digits. The blast radius is per-site (one WordPress database, one set of member PII), not domain-wide or fleet-scale. There is no confirmed in-the-wild exploitation, no KEV listing, and EPSS sits at 0.48% (39th percentile). The attack is dangerous where it applies, but the installed-base narrowness and per-site containment justify a downgrade to HIGH 8.5.
6 steps from start to impact.
Identify ARMember installation
/wp-content/plugins/armember/ path probing, readme.txt version disclosure, or known front-end CSS/JS asset paths. Automated scanners like WPScan or Nuclei templates can enumerate this at scale.- Target runs WordPress with ARMember Premium ≤ 7.3.1
- Plugin assets or directory pages are publicly accessible
- ARMember Premium has only ~30K-50K installs globally — most WordPress sites do not run it
- Version disclosure in
readme.txtmay be suppressed by hardened deployments
Extract nonce and template ID from directory page
arm_template_id value. The attacker visits any public directory page and scrapes these values from the HTML source. These tokens are required to call the arm_directory_paging_action AJAX handler.- At least one ARMember directory page exists and is publicly accessible
- If no directory page is configured or it is restricted to logged-in users, this step fails
- Some WAFs strip or obfuscate nonce values in responses
Unauthenticated SQL injection via CVE-2026-5073
/wp-admin/admin-ajax.php with action=arm_directory_paging_action and a malicious order parameter. The arm_get_directory_members() function concatenates the unsanitized order value directly into the SQL query. The PoCs use an error-based boolean oracle technique (IF() with EXP(710) to trigger MySQL overflow) enabling blind extraction character by character. Tools: zycoder0day/CVE-2026-5076 and shootcannon/CVE-2026-5076 automate this with binary search.- Valid nonce and template ID obtained in step 2
- The
arm_directory_paging_actionAJAX endpoint is reachable (registered viawp_ajax_nopriv_)
- Web Application Firewalls (ModSecurity, Wordfence, Cloudflare WAF) may detect SQLi payloads in POST parameters — but error-based oracle payloads are subtler than UNION-based
- Rate limiting on
admin-ajax.phpwould slow binary-search extraction significantly
admin-ajax.php, MySQL error log spikes from EXP(710) overflowTrigger password reset for admin user
lostpassword) for the target admin username or email. This causes the plugin to generate a 20-character reset key and store it in plaintext in the arm_reset_password_key user meta field. The attacker does not need the email — they just need to trigger the write to the database.- Admin username or email is known or guessable (often
admin, or enumerable via?author=1)
- WordPress username enumeration can be blocked with plugins or server config
- Some hosting providers block the
lostpasswordaction for admin accounts
Extract plaintext reset key via SQLi
wp_usermeta, the attacker uses the same SQLi channel from step 3 to read arm_reset_password_key for the target user ID. The binary search technique extracts the 20-character key in approximately 100–140 HTTP requests. A critical persistence bug means get_password_reset_key() does not update arm_reset_password_key, so the plaintext key remains valid indefinitely until manually deleted.- SQLi channel from step 3 is still usable
- Plaintext key exists in
wp_usermeta(step 4 succeeded)
- Same WAF/rate-limiting friction as step 3
- If the database prefix is non-standard, the attacker needs an extra enumeration round
wp_usermeta reads for arm_reset_password_key at the database audit levelReset admin password and log in
/?armrp=true&key=<EXTRACTED_KEY>&login=<admin_username> and submits a new password via the resulting form. They then log in to /wp-admin/ with full administrative privileges. From here, the attacker can install backdoor plugins, exfiltrate member data and payment information, modify site content, or pivot to the underlying server via the WordPress theme/plugin editor.- Extracted reset key is valid (it persists until manually purged)
- ARMember's
armrpendpoint is functional
- Two-factor authentication on the WordPress admin account would block login even after password reset
- IP-based admin login restrictions would limit access
arm_directory_paging_action (CVE-2026-5073), and the auth SQLi in get_private_content_data (CVE-2026-5074). Per the noisgate mitigation SLA for HIGH, deploy within 30 days. Given public PoCs exist, treat this as the top priority among your WordPress patching queue.DELETE FROM wp_usermeta WHERE meta_key = 'arm_reset_password_key'; on every affected WordPress database. The plaintext keys persist indefinitely due to a logic bug where get_password_reset_key() does not update the ARMember meta field. Even after patching to 7.3.2, stale keys may remain exploitable if a new SQLi surfaces. Do this within 3 days alongside or before patching./wp-admin/admin-ajax.php for SQL injection patterns. This breaks Step 3 of the attack chain. Error-based oracle payloads using EXP(710) and IF() constructs should be blockable with tuned rules. Deploy within 30 days per the noisgate mitigation SLA.UPDATE wp_usermeta SET meta_value = '' WHERE meta_key = 'session_tokens';). Notify members if PII exposure is suspected. Execute within 3 days on any site confirmed to run vulnerable versions.- IP allowlisting on wp-login.php alone — the attack chain does not go through wp-login.php until the final login step; the SQLi and password reset happen via
admin-ajax.phpand thearmrpfront-end endpoint, both of which bypass wp-login restrictions. - WordPress core auto-updates — ARMember Premium is sold via CodeCanyon, not the wordpress.org repository, so WordPress core auto-update mechanisms do not cover it. Manual update or CodeCanyon license-linked update is required.
- Disabling XML-RPC — the attack uses AJAX endpoints (
admin-ajax.php), not XML-RPC. Disabling XML-RPC provides no protection against this chain. - Database-level encryption at rest — the SQLi reads data through the application layer (MySQL queries via WordPress), so TDE or disk encryption does not prevent the attacker from extracting plaintext keys.
The supporting signals.
| In-the-wild exploitation | No confirmed exploitation. Not listed on CISA KEV. No GreyNoise tags or Shadowserver scan reports observed as of 2026-09-27. Proofpoint's 2026 routinely-targeted list does not include this CVE. |
|---|---|
| Proof-of-concept availability | 2 public PoCs on GitHub — zycoder0day/CVE-2026-5076 (published 2026-06-04, credited XENON1337 & ENDANG) and shootcannon/CVE-2026-5076 (published 2026-06-05). Both automate the full 7-phase chain including SQLi extraction and password reset. Listed on pocindex.io with 2 entries from GitHub. |
| EPSS | 0.0048 (0.48%) — 39th percentile. Low predicted exploitation probability, though PoC availability often precedes EPSS catch-up by 30–90 days. |
| CISA KEV status | Not listed as of 2026-09-27. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — Network-reachable, low complexity, no privileges, no interaction. The vector assumes the full chain with CVE-2026-5073 is available, which is accurate since both ship in the same plugin version range. |
| Affected versions | ARMember Premium 5.x through 7.3.1 (all versions). The free/lite version on wordpress.org is not affected — this is the premium (CodeCanyon) edition only. |
| Fixed version | ARMember Premium 7.3.2 — fixes CVE-2026-5076 (plaintext key storage), CVE-2026-5073 (unauth SQLi via order), and CVE-2026-5074 (auth SQLi via sSortDir_0). |
| Install base & exposure | ~30,000–50,000 active premium installations per vendor and PoC documentation. WordPress sites are internet-facing by nature; the vulnerable AJAX endpoint is reachable at /wp-admin/admin-ajax.php on every installation. |
| Disclosure timeline | Discovered by Wordfence Threat Intelligence, disclosed 2026-06-02, NVD published 2026-06-02, public PoCs appeared 2026-06-04/05. Patch (7.3.2) was available at disclosure. |
| Related CVEs | CVE-2026-5073 (CVSS 7.5, unauth SQLi via order param) and CVE-2026-5074 (CVSS 6.5, auth SQLi via sSortDir_0, requires Subscriber + Private Content addon enabled). CVE-2026-5073 is the critical chain link enabling unauthenticated exploitation of CVE-2026-5076. |
Sources.
- Wordfence Threat Intel — CVE-2026-5076 Advisory
- GitHub Advisory GHSA-53mc-7vx2-cm58
- NVD — CVE-2026-5076
- PoC: zycoder0day/CVE-2026-5076 (full chain exploit)
- PoC: shootcannon/CVE-2026-5076 (admin takeover)
- BitNinja — Critical Vulnerability Alert: ARMember Security Risk
- pocindex.io — CVE-2026-5076 PoC listing
- OpenCVE — CVE-2026-5073 (chained SQLi)
Why this verdict
- Chain dependency is real but colocated: CVE-2026-5076 alone is a design flaw (plaintext key storage) that requires a separate read primitive. That primitive — CVE-2026-5073 (unauth SQLi) — ships in the *same plugin, same version range*, and both are fixed in 7.3.2. Still, the vendor's 9.8 scores the chain as a single monolithic attack; the conceptual dependency is a minor downward pressure (~0.3 points).
- Niche install base: ARMember Premium has ~30K-50K installs versus millions for WooCommerce or Elementor. In a 10,000-host enterprise, the expected number of affected hosts is 0–3. This dramatically reduces fleet-level risk exposure (~0.5 points downward).
- No confirmed exploitation and low EPSS: Despite PoCs being public for nearly 4 months, there is no KEV listing, no GreyNoise/Shadowserver scan data, and EPSS is at 0.48% (39th percentile). The exploitation window has been open but apparently underutilized (~0.2 points downward).
- WAF friction on the SQLi step: The enabling SQLi (CVE-2026-5073) transits through
admin-ajax.phpPOST parameters. Enterprise-grade WAFs (Cloudflare, ModSecurity CRS, Wordfence Pro) with SQL injection rulesets will catch many payloads, though error-based oracle techniques are subtler than UNION-based injections (~0.2 points downward). - Role multiplier: ARMember Premium is a WordPress membership/subscription plugin. (a) *Low-value role*: dev/staging WordPress instances — chain succeeds, blast radius = one dev site, negligible. (b) *Typical role*: SMB membership site with member PII and possibly payment data — chain succeeds, blast radius = one site's data + potential web shell, contained to that host. (c) *High-value role assessment*: WordPress membership sites are not in the canonical high-value-role catalog (not IdP, DC, hypervisor, CI/CD, backup, PKI, network edge, SIEM, or OT). A compromised WordPress site could theoretically serve as a pivot point, but it is not fleet-scale or identity-scale. The floor rule does not apply. Per-site blast radius caps the worst-case outcome at host-level compromise + data exfiltration for that site's member database.
Why not higher?
The attack chain is fully unauthenticated and automated with public PoCs, which would normally justify CRITICAL. However, the blast radius is contained to individual WordPress sites, the affected plugin is niche (~30K-50K installs, not a canonical high-value infrastructure component), and there is no evidence of active exploitation after nearly 4 months of PoC availability. The floor rule for CRITICAL requires the component to canonically occupy a high-value role (≥10% of installs), which ARMember does not.
Why not lower?
Despite the niche install base, this is a fully unauthenticated, zero-interaction, remotely exploitable admin takeover with working automation. Two independent PoC authors published full exploit chains within 72 hours of disclosure. Any WordPress site running ARMember Premium ≤ 7.3.1 with a public directory page is trivially compromisable. The plaintext key persists indefinitely due to a logic bug, meaning even a stale reset key works. Dropping below HIGH would understate the severity for the sites that *are* affected.
Crowdsourced verification payload.
Run this script on the WordPress host (the server where wp-content lives) as any user with read access to the WordPress installation directory. Invoke with the path to the WordPress root: bash check_cve_2026_5076.sh /var/www/html. No root privileges required — just filesystem read access to the plugin directory and wp-config.php.
#!/usr/bin/env bash
# check_cve_2026_5076.sh — Detect ARMember Premium CVE-2026-5076
# Usage: bash check_cve_2026_5076.sh /path/to/wordpress
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
WP_ROOT="${1:?Usage: $0 /path/to/wordpress}"
PLUGIN_DIR="$WP_ROOT/wp-content/plugins/armember"
CHANGELOG="$PLUGIN_DIR/changelog.txt"
CORE_FILE="$PLUGIN_DIR/armember.php"
# Check if plugin exists
if [ ! -d "$PLUGIN_DIR" ]; then
echo "UNKNOWN — ARMember Premium plugin not found at $PLUGIN_DIR"
exit 2
fi
# Try to extract version from main plugin file
VERSION=""
if [ -f "$CORE_FILE" ]; then
VERSION=$(grep -i 'Version:' "$CORE_FILE" | head -1 | sed 's/.*Version:[[:space:]]*//' | tr -d '[:space:]')
fi
# Fallback: check readme.txt
if [ -z "$VERSION" ] && [ -f "$PLUGIN_DIR/readme.txt" ]; then
VERSION=$(grep -i 'Stable tag:' "$PLUGIN_DIR/readme.txt" | head -1 | sed 's/.*Stable tag:[[:space:]]*//' | tr -d '[:space:]')
fi
if [ -z "$VERSION" ]; then
echo "UNKNOWN — Could not determine ARMember Premium version"
exit 2
fi
echo "Detected ARMember Premium version: $VERSION"
# Compare versions: vulnerable if <= 7.3.1
# Using sort -V for version comparison
MIN_SAFE="7.3.2"
SORTED_FIRST=$(printf '%s\n%s' "$VERSION" "$MIN_SAFE" | sort -V | head -1)
if [ "$SORTED_FIRST" = "$MIN_SAFE" ] || [ "$VERSION" = "$MIN_SAFE" ]; then
# VERSION >= MIN_SAFE
echo "PATCHED — Version $VERSION is >= $MIN_SAFE (CVE-2026-5076 fixed)"
exit 0
else
# VERSION < MIN_SAFE
echo "VULNERABLE — Version $VERSION is below $MIN_SAFE (CVE-2026-5076 applies)"
# Bonus: check for stale plaintext reset keys in the database
if [ -f "$WP_ROOT/wp-config.php" ]; then
DB_NAME=$(grep "DB_NAME" "$WP_ROOT/wp-config.php" | cut -d "'" -f 4 2>/dev/null || true)
DB_USER=$(grep "DB_USER" "$WP_ROOT/wp-config.php" | cut -d "'" -f 4 2>/dev/null || true)
DB_PASS=$(grep "DB_PASSWORD" "$WP_ROOT/wp-config.php" | cut -d "'" -f 4 2>/dev/null || true)
DB_HOST=$(grep "DB_HOST" "$WP_ROOT/wp-config.php" | cut -d "'" -f 4 2>/dev/null || true)
PREFIX=$(grep "table_prefix" "$WP_ROOT/wp-config.php" | cut -d "'" -f 2 2>/dev/null || true)
PREFIX=${PREFIX:-wp_}
if [ -n "$DB_NAME" ] && command -v mysql &>/dev/null; then
KEY_COUNT=$(mysql -h"${DB_HOST:-localhost}" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -sNe \
"SELECT COUNT(*) FROM ${PREFIX}usermeta WHERE meta_key='arm_reset_password_key' AND meta_value != '';" 2>/dev/null || echo "?")
if [ "$KEY_COUNT" != "?" ] && [ "$KEY_COUNT" -gt 0 ] 2>/dev/null; then
echo "WARNING — $KEY_COUNT plaintext password reset key(s) found in ${PREFIX}usermeta"
fi
fi
fi
exit 1
fi