This is a booby-trapped sticky note, not a master key
CVE-2025-22320 is a reflected XSS issue in the WordPress productdyno plugin affecting versions through 1.0.24. The bug is tied to unsafely handled user input during page generation; Wordfence later described the same vulnerable behavior more concretely as the res parameter, and NVD notes that CVE-2024-13413 is potentially a duplicate of this issue. The vendor-side fix is to move to 1.0.25 or later.
The vendor's HIGH 7.1 score is too generous for real enterprise prioritization. Yes, it is unauthenticated and internet-reachable in theory, but it is still reflected, still requires user interaction, and it sits in a plugin with very small exposure — WordPress.org shows roughly 70+ active installs and Wordfence shows 80. That combination crushes the reachable population and makes this a cleanup patch, not a fire drill.
3 steps from start to impact.
Fingerprint a site running productdyno
WPScan; version detection may also come from local file reads or authenticated plugin inventory rather than clean remote fingerprinting.- Target runs WordPress
- Target has the
productdynoplugin installed - Installed version is
<= 1.0.24
- The plugin's population is tiny: WordPress.org shows 70+ active installs and Wordfence shows 80
- Remote version fingerprinting is often noisy or incomplete
- Many enterprise WordPress estates do not use this niche membership plugin at all
Deliver a crafted reflected-XSS link
Burp Suite, a phishing email, chat message, support ticket, or malicious redirect chain.- Attacker can reach the vulnerable page over HTTP/HTTPS
- Victim can be induced to click or load attacker-controlled content
- The vulnerable parameter is reflected into the response in executable context
- This is not self-triggering; no click, no bug
- Modern email gateways, browser protections, and user training reduce delivery success
- A reflected XSS against a low-install plugin usually requires more bespoke targeting than spray-and-pray exploitation
res= values if request logging is retained.Abuse the victim browser session
BeEF.- Victim opens the crafted URL
- Browser allows script execution in the reflected context
- Victim has a session worth stealing or authority worth abusing
- Impact is user-context-dependent; anonymous visitors offer limited value
- HttpOnly cookies, CSRF protections, same-origin restrictions, and admin workflow separation limit post-XSS leverage
- MFA is irrelevant after session establishment, but it still reduces the value of pure credential capture
The supporting signals.
| In-the-wild status | No public exploitation evidence found in the sources reviewed, and the issue is not listed in CISA KEV. |
|---|---|
| Public PoC status | No credible public PoC or weaponized exploit repo found in GitHub/Exploit-DB style searches. Public writeups exist, but not a widely circulated exploit chain. |
| EPSS | User-supplied EPSS is 0.00241 (0.241%), which is very low. A separate Feedly snapshot surfaced 0.04% with 11.1 percentile; EPSS is daily and source snapshots can drift, but both readings point to low exploitation likelihood. |
| KEV status | Not in CISA Known Exploited Vulnerabilities based on the catalog reviewed. |
| CVSS vector meaning | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L says easy network delivery with no auth, but it also admits the most important real-world brake: UI:R. If a human must be tricked, this is not wormable internet-edge risk. |
| Affected versions | All productdyno versions through 1.0.24 are affected per Patchstack/NVD. |
| Fixed version | 1.0.25 or later fixes the issue per Patchstack. WordPress.org currently shows the plugin beyond that, with 1.0.26 listed. |
| Exposure population | This is the biggest downgrade driver: WordPress.org shows 70+ active installs and Wordfence lists 80 active installs / 6,435 downloads. That is a tiny attack surface compared with mainstream WordPress plugin incidents. |
| Disclosure and reporting | Patchstack/CNA published CVE-2025-22320 on 2025-01-07 and credits Jorge Diaz (ddiax). Patchstack's page shows publication on 2025-01-03. |
| Record quality caveat | NVD for CVE-2024-13413 says the later Wordfence record describing the res parameter is potentially a duplicate of CVE-2025-22320. That duplication does not raise risk, but it does tell you the public recordkeeping around this bug is a bit messy. |
noisgate verdict.
The single decisive factor is population and reachability: this is a reflected XSS in a plugin with roughly 70-80 active installs, so the exposed universe is already microscopic before the attacker even starts social engineering. Add the mandatory victim click and user-context dependency, and the vendor's HIGH rating stops matching operational reality.
Why this verdict
- Downgrade for attacker friction: exploitation requires user interaction (
UI:R). The attacker must get a victim to open a crafted URL; that is a real break in the kill chain, not paperwork. - Downgrade for exposure population: WordPress.org and Wordfence both show a tiny install base. A bug in a plugin with ~70-80 active installs does not deserve the same queue position as a broadly deployed edge product.
- Downgrade for impact dependency: real damage depends on who clicks. Anonymous visitor impact is usually minor; meaningful compromise generally needs an authenticated admin or privileged editor session.
- Downgrade for telemetry: no KEV listing, no campaign reporting, no public PoC signal in the reviewed sources. Threat pressure is low.
Why not higher?
This is not stored XSS, not pre-auth RCE, and not a mass-scannable takeover primitive. There is no evidence of active exploitation, no large exposed population, and no reason to believe modern attackers will spend time developing bespoke lures for a plugin with so few deployments.
Why not lower?
It is still a real unauthenticated web bug in an internet-facing component, and reflected XSS can become serious if a logged-in admin opens the link. If you do happen to run this plugin on a revenue site or member portal, the browser-session abuse path is credible enough that this should not be ignored entirely.
What to do — in priority order.
- Upgrade the plugin — Move
productdynoto 1.0.25 or later. For a LOW verdict there is no mitigation SLA and no formal remediation SLA in noisgate; treat it as backlog hygiene and fold it into the normal WordPress plugin maintenance cycle. - Restrict admin exposure — Keep
/wp-admin/and privileged WordPress workflows behind VPN, identity-aware proxy, or IP allowlists where practical. That does not fix the XSS, but it shrinks the chance that a high-value admin session is the browser context that gets hit. - Turn on plugin-aware WordPress security — Use Wordfence, Patchstack, or equivalent plugin-aware controls to flag vulnerable versions and block obvious XSS payloads. Because there is no LOW mitigation SLA, deploy this as part of routine hardening rather than an emergency change.
- Review click-path telemetry — Watch reverse proxy, WAF, and web logs for suspicious requests carrying unexpected script-like values in the vulnerable parameter. This is useful for hunting and validation, especially on customer-facing sites where reflected-XSS delivery usually leaves a URL trail.
- Plain EDR on the web server does not stop JavaScript from executing in the victim's browser; the exploit lives in the client session.
- MFA alone does not solve browser-session abuse once an authenticated admin has already loaded the malicious page.
- Perimeter vulnerability scans without WordPress plugin awareness often miss niche plugin versioning and give false comfort.
Crowdsourced verification payload.
Run this on the target WordPress host or from a mounted backup of the site's filesystem. Invoke it as bash check-productdyno-cve-2025-22320.sh /var/www/html/wp-content/plugins/productdyno; it needs read access to the plugin directory only, so root is not required unless your web files are restricted.
#!/usr/bin/env bash
# check-productdyno-cve-2025-22320.sh
# Detects whether the WordPress ProductDyno plugin is vulnerable to CVE-2025-22320
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN / usage / unreadable
set -u
PLUGIN_DIR="${1:-}"
if [[ -z "$PLUGIN_DIR" ]]; then
echo "UNKNOWN - usage: $0 /path/to/wp-content/plugins/productdyno"
exit 2
fi
if [[ ! -d "$PLUGIN_DIR" ]]; then
echo "UNKNOWN - directory not found: $PLUGIN_DIR"
exit 2
fi
MAIN_FILE=""
for f in "$PLUGIN_DIR"/*.php; do
[[ -e "$f" ]] || continue
if grep -qiE '^\s*Version:\s*' "$f" 2>/dev/null; then
MAIN_FILE="$f"
break
fi
done
README_FILE=""
if [[ -f "$PLUGIN_DIR/readme.txt" ]]; then
README_FILE="$PLUGIN_DIR/readme.txt"
fi
extract_version() {
local file="$1"
local v
v=$(grep -iE '^\s*Version:\s*' "$file" 2>/dev/null | head -n1 | sed -E 's/^\s*Version:\s*//I' | tr -d '\r' | xargs)
if [[ -n "$v" ]]; then
echo "$v"
return 0
fi
return 1
}
extract_stable_tag() {
local file="$1"
local v
v=$(grep -iE '^\s*Stable tag:\s*' "$file" 2>/dev/null | head -n1 | sed -E 's/^\s*Stable tag:\s*//I' | tr -d '\r' | xargs)
if [[ -n "$v" ]]; then
echo "$v"
return 0
fi
return 1
}
VERSION=""
SOURCE=""
if [[ -n "$MAIN_FILE" ]]; then
VERSION=$(extract_version "$MAIN_FILE" || true)
if [[ -n "$VERSION" ]]; then
SOURCE="$MAIN_FILE"
fi
fi
if [[ -z "$VERSION" && -n "$README_FILE" ]]; then
VERSION=$(extract_stable_tag "$README_FILE" || true)
if [[ -n "$VERSION" ]]; then
SOURCE="$README_FILE"
fi
fi
if [[ -z "$VERSION" ]]; then
echo "UNKNOWN - could not determine ProductDyno version from plugin files"
exit 2
fi
normalize() {
echo "$1" | sed 's/[^0-9.].*$//'
}
VERSION_N=$(normalize "$VERSION")
if [[ -z "$VERSION_N" ]]; then
echo "UNKNOWN - unparsable version: $VERSION"
exit 2
fi
# Compare semantic-ish versions using sort -V
verlte() { [[ "$1" == "$2" ]] || [[ "$(printf '%s
%s
' "$1" "$2" | sort -V | head -n1)" == "$1" ]]; }
verlt() { [[ "$1" != "$2" ]] && verlte "$1" "$2"; }
vergte() { [[ "$1" == "$2" ]] || [[ "$(printf '%s
%s
' "$1" "$2" | sort -V | tail -n1)" == "$1" ]]; }
FIXED="1.0.25"
if verlte "$VERSION_N" "1.0.24"; then
echo "VULNERABLE - ProductDyno version $VERSION_N detected from $SOURCE (fixed in $FIXED)"
exit 1
elif vergte "$VERSION_N" "$FIXED"; then
echo "PATCHED - ProductDyno version $VERSION_N detected from $SOURCE"
exit 0
else
echo "UNKNOWN - detected version $VERSION_N but could not classify confidently"
exit 2
fi
If you remember one thing.
productdyno exists anywhere at all; in most enterprises it probably will not. If present, upgrade to 1.0.25+ during normal WordPress maintenance and document the tiny exposure footprint as the reason this is LOW. There is no noisgate mitigation SLA and no noisgate remediation SLA for LOW severity — treat it as backlog hygiene rather than an emergency, but do not let abandoned low-install WordPress plugins sit unowned indefinitely.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.