This is a spare key taped under the doormat, but only after the attacker gets onto the porch
CVE-2026-6895 affects the commercial wishlist-member-x WordPress plugin in versions up to and including 3.30.1 and is fixed in 3.31.0. A low-privileged authenticated user can call the vulnerable export_settings path, receive the WishList Member REST API key in an AJAX response, then use that key to create a membership level mapped to the WordPress administrator role and mint a new admin account.
The vendor's 8.8/HIGH score is technically fair in a lab because the end state is full site takeover, but it overstates broad enterprise urgency. The chain starts with PR:L, which in the real world means either open self-registration, cheap paid signup, or an already-compromised subscriber account; that is a real friction point, and the plugin's overall population is small enough that this is not a mass-internet emergency absent exploitation evidence.
4 steps from start to impact.
Fingerprint a live WishList Member target
3.31.0, typically with normal web recon or a tool like WPScan. This is not an exploit step by itself, but it narrows the target set to a relatively small commercial-plugin population.- The site runs WordPress with the
wishlist-member-xplugin installed - The attacker can reach the public site over HTTPS
- WishList Member is not a broadly deployed commodity plugin compared with top free WordPress extensions
- Precise version fingerprinting is often obscured unless plugin files or metadata are exposed
WPScan can usually identify the plugin, but they do not prove exploitability or whether public registration makes the bug reachable.Get a subscriber foothold
- The attacker can register, buy, or otherwise obtain a low-privileged account
- The account is allowed to log in and hit plugin AJAX functionality
- Many membership sites gate signups behind payment, approval, captcha, or email confirmation
- If the site has no public registration path, this becomes post-initial-access only
Call wlm3_export_settings and steal the API key
Burp, or curl, the attacker invokes the vulnerable AJAX action that lacks a proper capability check. The response leaks the WishList Member REST API key, which converts a normal subscriber session into API-level administrative reach over the plugin.- The vulnerable code path is present in
<= 3.30.1 - The subscriber session is valid
- The plugin's API key remains active
- Web application firewalls or custom
admin-ajax.phphardening may block unusual request patterns - Some sites may rotate or disable API access during security hardening
wp-admin/admin-ajax.php invoking WishList actions, especially followed by API calls from the same user or IP.Use the API to create an admin path and take over the site
POST new membership levels and set wordpress_role=administrator; the attacker can then register or promote an account into that level. At that point this is standard WordPress full-site compromise: install plugins, alter content, dump data, or create persistence.- The leaked API key is accepted by the WishList Member API
- API endpoints are reachable from the internet
- The site accepts the created role mapping and new user workflow
- Attackers still need to understand the plugin's API workflow rather than pressing a one-click public exploit button
- Operational mistakes are noisy because new levels, new users, and role changes leave artifacts
?/wlmapi/2.0/, and any new administrator accounts shortly after suspicious subscriber activity.The supporting signals.
| In-the-wild status | No official active-exploitation signal found in the retrieved sources. CISA KEV has no record for CVE-2026-6895, and I found no public campaign reporting tied to this CVE. |
|---|---|
| PoC availability | Public write-up exists at Atomic Edge, but I did not find a widely circulated vendor-verified exploit repo in the retrieved sources. |
| EPSS | User-supplied EPSS is 0.00044 (0.044%), which is very low. Official FIRST sources retrieved here document the API/methodology, but the percentile for this specific CVE was not directly exposed in the fetched results. |
| KEV status | Not listed in the CISA KEV catalog as checked against retrieved search results. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — the critical nuance is PR:L: this is not an unauthenticated internet bug. |
| Affected versions | Authoritative sources consistently place the vulnerable range at <= 3.30.1 / < 3.31.0 for the wishlist-member-x plugin. |
| Fixed version | 3.31.0 per WPScan and Patchstack. The vendor 3.31.0 changelog lists security fixes, though it does not name CVE-2026-6895 explicitly. |
| Exposure / population | This is a commercial WordPress membership plugin, not a ubiquitous default component. Wordfence's plugin page shows about 4,245 active installs, while the vendor markets 120,310 cumulative site activations since 2008; either way, reachable enterprise population is far smaller than mainstream WordPress plugin emergencies. |
| Disclosure timeline | NVD shows publication on 2026-05-23; WPScan shows public publication on 2026-05-22; Patchstack published its entry on 2026-05-25. |
| Researcher / source | WPScan and Patchstack credit h0xilo; NVD lists Wordfence as the CNA/source for the CVE record. |
noisgate verdict.
The decisive downward pressure is the attacker-position requirement: this bug starts at authenticated subscriber, not unauthenticated remote. That means many enterprises only care if they run public-facing membership sites with self-registration or if an attacker already has a foothold in the WordPress tenant.
Why this verdict
- Start from 8.8, then cut for attacker position: vendor scoring assumes full-value internet reach, but
PR:Lmeans the attacker needs a subscriber account before anything interesting happens. - Exposure population is narrow: WishList Member is a niche commercial membership plugin, not a mass-installed core dependency. Even the higher vendor marketing number reflects cumulative activations, not current exposed fleet.
- The prerequisite often implies prior compromise or business-process abuse: if registration is closed, paid, approved, or invite-only, exploitation becomes post-initial-access rather than first-hop internet exploitation.
- No exploitation evidence is doing more work than the CVSS does: no
KEV, no public campaign reporting, and a very low user-supplied EPSS all argue against emergency fleet-wide treatment. - Impact is still real: once a subscriber can hit the vulnerable action, the chain leads to admin creation and full WordPress takeover, so this is not backlog trash.
Why not higher?
This is not a clean unauthenticated remote takeover. The exploit chain depends on a low-privileged account and a plugin that is comparatively narrow in deployment, which compounds the reachable-population problem. In enterprise triage terms, that is a substantial downgrade from the vendor's lab score.
Why not lower?
The post-auth impact is complete site compromise, not just data leakage or a cosmetic role bug. On any affected site with open or easy self-registration, the friction collapses fast and a low-skill attacker can plausibly buy or create their way to admin.
What to do — in priority order.
- Disable public self-registration on affected sites — If the site does not strictly need anonymous signup, close the cheapest path into
PR:L. For aMEDIUMverdict there is no mitigation SLA — go straight to the 365-day remediation window, but do this early on any internet-facing membership site because it materially changes exploitability. - Regenerate the WishList Member API key — The exploit chain turns on disclosure of the plugin's API credential, so rotate it anywhere exposure is suspected and invalidate any copied key. There is no mitigation SLA — go straight to the 365-day remediation window, but key rotation should be bundled with patch validation on exposed sites.
- Monitor and, if needed, block suspicious WishList AJAX and API paths — Add temporary WAF or reverse-proxy controls around suspicious authenticated requests to
wp-admin/admin-ajax.phpfor WishList actions and unusual traffic to?/wlmapi/2.0/. There is no mitigation SLA — go straight to the 365-day remediation window, so use this as selective hardening where patching is delayed. - Audit low-tier accounts and recent admin creation — Review subscriber signups, low-cost membership purchases, new membership levels, and any recently created administrator accounts for compromise artifacts. There is no mitigation SLA — go straight to the 365-day remediation window, but this is the fastest way to tell whether the bug was already abused.
- Resetting only administrator passwords does not fix the issue; the attacker path is to create a fresh admin via plugin/API logic.
- Generic perimeter blocking of
wp-login.phpis incomplete; a valid subscriber session can come from normal site registration and then exploitadmin-ajax.phpand plugin API endpoints. - Assuming MFA on existing admins fully solves it is risky; it helps only if MFA is enforced on any newly created admin account and across all WordPress admin entry paths.
Crowdsourced verification payload.
Run this on the target WordPress host or inside the application container, pointing it at the WordPress document root. Example: bash verify-cve-2026-6895.sh /var/www/html; it needs only read access to the plugin files, so root is usually unnecessary unless your web root is locked down.
#!/usr/bin/env bash
# verify-cve-2026-6895.sh
# Check whether a local WordPress install appears vulnerable to CVE-2026-6895
# Output: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 1=vulnerable, 0=patched, 2=unknown/error
set -u
TARGET="${1:-}"
if [[ -z "$TARGET" ]]; then
echo "UNKNOWN - usage: $0 /path/to/wordpress-root"
exit 2
fi
if [[ ! -d "$TARGET" ]]; then
echo "UNKNOWN - path does not exist: $TARGET"
exit 2
fi
PLUGINS_DIR="$TARGET/wp-content/plugins"
if [[ ! -d "$PLUGINS_DIR" ]]; then
echo "UNKNOWN - wp-content/plugins not found under: $TARGET"
exit 2
fi
# Compare versions using sort -V
ver_lt() {
[[ "$1" != "$2" ]] && [[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" == "$1" ]]
}
ver_le() {
[[ "$1" == "$2" ]] || ver_lt "$1" "$2"
}
extract_version_from_file() {
local f="$1"
local v
v=$(head -n 80 "$f" 2>/dev/null | sed -nE 's/^[[:space:]]*\*?[[:space:]]*Version:[[:space:]]*([^[:space:]]+).*/\1/p' | head -n1)
if [[ -n "$v" ]]; then
echo "$v"
return 0
fi
return 1
}
extract_stable_tag() {
local f="$1"
local v
v=$(sed -nE 's/^[[:space:]]*Stable tag:[[:space:]]*([^[:space:]]+).*/\1/p' "$f" 2>/dev/null | head -n1)
if [[ -n "$v" ]]; then
echo "$v"
return 0
fi
return 1
}
find_plugin_dir() {
local d
for d in \
"$PLUGINS_DIR/wishlist-member-x" \
"$PLUGINS_DIR/wishlist-member" \
"$PLUGINS_DIR/WishListMember"; do
if [[ -d "$d" ]]; then
echo "$d"
return 0
fi
done
d=$(find "$PLUGINS_DIR" -maxdepth 1 -mindepth 1 -type d \( -iname 'wishlist-member*' -o -iname 'wishlistmember*' \) 2>/dev/null | head -n1)
if [[ -n "$d" ]]; then
echo "$d"
return 0
fi
return 1
}
PLUGIN_DIR=$(find_plugin_dir) || {
echo "UNKNOWN - WishList Member plugin directory not found"
exit 2
}
VERSION=""
# Preferred candidate files
for f in \
"$PLUGIN_DIR/wishlist-member.php" \
"$PLUGIN_DIR/wishlist-member-x.php" \
"$PLUGIN_DIR/plugin.php" \
"$PLUGIN_DIR/wpm.php" \
"$PLUGIN_DIR/index.php"; do
if [[ -f "$f" ]]; then
VERSION=$(extract_version_from_file "$f" || true)
[[ -n "$VERSION" ]] && break
fi
done
# Fallback: first PHP file with a Version header near the top
if [[ -z "$VERSION" ]]; then
while IFS= read -r -d '' f; do
VERSION=$(extract_version_from_file "$f" || true)
[[ -n "$VERSION" ]] && break
done < <(find "$PLUGIN_DIR" -maxdepth 2 -type f -name '*.php' -print0 2>/dev/null)
fi
# Final fallback: readme stable tag
if [[ -z "$VERSION" ]]; then
for f in "$PLUGIN_DIR/readme.txt" "$PLUGIN_DIR/readme.md"; do
if [[ -f "$f" ]]; then
VERSION=$(extract_stable_tag "$f" || true)
[[ -n "$VERSION" ]] && break
fi
done
fi
if [[ -z "$VERSION" ]]; then
echo "UNKNOWN - could not determine WishList Member version in $PLUGIN_DIR"
exit 2
fi
if ver_le "$VERSION" "3.30.1"; then
echo "VULNERABLE - WishList Member version $VERSION detected in $PLUGIN_DIR"
exit 1
fi
if ! ver_lt "$VERSION" "3.31.0"; then
echo "PATCHED - WishList Member version $VERSION detected in $PLUGIN_DIR"
exit 0
fi
# This should be rare, but keeps ambiguous versions explicit.
echo "UNKNOWN - WishList Member version $VERSION detected; unable to map confidently"
exit 2
If you remember one thing.
wishlist-member-x, then split them into two buckets: public membership sites with self-registration and everything else. For this MEDIUM reassessment there is noisgate mitigation SLA — no mitigation SLA — go straight to the 365-day remediation window — but exposed membership sites should still get first attention for registration hardening, API-key rotation, and log review while you validate upgrades. Apply the vendor fix to 3.31.0+ within the noisgate remediation SLA of <= 365 days, with internet-facing sites that allow easy subscriber creation patched first.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.