This is a booby-trapped draft waiting for an editor to open it
CVE-2025-22310 is a stored XSS flaw in the WordPress plugin TemplatesNext ToolKit (templatesnext-toolkit) affecting all versions through 3.2.9. A user with at least Contributor privileges can plant script-bearing content that is stored server-side and later executed in another user's browser when the poisoned page or post is viewed. The plugin was later closed on WordPress.org on 2025-02-27 for a security issue, and public advisories still report no official fix available.
The vendor/CNA MEDIUM 6.5 score is technically fair, but it overstates enterprise urgency. The decisive friction is that exploitation is not unauthenticated internet-to-server compromise; it requires a valid low-privilege WordPress account and then a second user to render the malicious content. That makes this a post-initial-access content-workflow bug, not a perimeter-breaker, though it can still matter on multi-author sites where contributors, editors, and admins routinely cross paths.
4 steps from start to impact.
Obtain contributor-level access
- Target runs the
templatesnext-toolkitplugin at version<= 3.2.9 - Attacker has authenticated WordPress access with
Contributoror higher - The site permits the attacker to create or edit content that hits the vulnerable rendering path
- Most enterprise WordPress sites do not expose anonymous author registration
- Contributor access already implies a prior foothold in the application
- SSO, MFA, and tighter role assignment cut the reachable population sharply
Plant stored JavaScript payload
- The attacker can reach the vulnerable input path in the plugin
- The plugin does not sanitize or escape the supplied value before later rendering
- Some editorial workflows keep contributor content in draft or pending-review state
- Moderation plugins, content sanitizers, or WAF rules may strip obvious payloads
- Not every deployment actively uses the vulnerable widget/shortcode path
<script> payloads but often miss obfuscated event handlers, SVG payloads, or shortcode abuse. Authenticated DAST coverage is uneven unless you test from a contributor session.Wait for a higher-privileged viewer
- A victim user loads the poisoned page, preview, or admin-side rendering view
- The victim browser executes the injected script
- The victim has privileges worth stealing or abusing
- UI interaction is mandatory
- If no admin/editor ever views attacker-authored content, the chain dies here
- Strict CSP, hardened cookies, and admin isolation can blunt some post-XSS actions
post.php or admin-ajax.php activity tied to contributor content.Abuse the victim session for follow-on actions
- Victim privileges are sufficient to perform interesting admin actions
- The site lacks compensating controls that limit XSS impact
- WordPress nonce boundaries and browser cookie protections can limit some actions
- Many high-impact goals still require precise knowledge of the site's admin flows
- This is narrower than unauthenticated RCE or arbitrary file upload
The supporting signals.
| In-the-wild status | No CISA KEV listing and I found no credible public reporting of active exploitation campaigns for this CVE specifically. That lines up with the very low EPSS and the post-auth workflow requirements. |
|---|---|
| Proof-of-concept availability | No public exploit repo or exploit-db entry surfaced in source review. That said, this is still easy to weaponize manually once an attacker has contributor access because stored XSS usually needs only crafted content, not a memory-corruption chain. |
| EPSS | 0.00152 (~0.152% probability over 30 days), which is very low and supports a downgrade from the raw vendor score for enterprise prioritization. |
| KEV status and dates | Not listed in CISA KEV. Public disclosure landed on 2025-01-07 in NVD/CNA records; the plugin was closed on WordPress.org on 2025-02-27 for a security issue. |
| CVSS vector and what it really means | CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L means network reachable, but not unauthenticated; it needs low privileges and user interaction. In plain English: this is a content-author abuse path, not a server-breakout. |
| Affected versions | All versions through 3.2.9 are reported vulnerable by NVD/Patchstack/Wordfence/WPScan. |
| Fixed version | No official fixed version is published in Patchstack, Wordfence, or WPScan. WordPress.org shows the plugin as closed, so the practical remediation is remove/replace, not wait for a patch that may never ship. |
| Exposure and reachability | Wordfence shows roughly 10,000 active installs and 561,764 downloads, so this is not tiny, but it is still a niche plugin population. I found no reliable Shodan/Censys/FOFA fingerprint that would let defenders cleanly estimate internet-exposed instances of this exact plugin. |
| Disclosure and researcher | Credited researcher: LVT-tholv2k. Patchstack and NVD date the public disclosure to 2025-01-06/2025-01-07 depending on feed normalization. |
| Historical signal | This plugin has a repeat XSS history: Wordfence/Patchstack list earlier stored-XSS issues in 2023 and another XSS entry in 2025. Recurrent input/output handling bugs are a negative maintenance signal and strengthen the case for replacement. |
noisgate verdict.
The single biggest downgrade driver is that exploitation requires authenticated Contributor access and then a victim to view the payload. That makes this a post-auth, workflow-dependent browser compromise with a materially smaller exposed population than the vendor score suggests, even though the absence of an official fix keeps it above LOW.
Why this verdict
- Downward pressure: requires authenticated remote access. The attacker must already hold a WordPress account with at least Contributor rights, which implies prior compromise, weak registration controls, or insider access.
- Downward pressure: user interaction is mandatory. A second user has to render the poisoned content before impact lands; plenty of malicious drafts die in moderation queues and never execute.
- Downward pressure: limited deployment slice. This is a single niche plugin with about 10,000 installs, not WordPress core or a top-tier plugin with massive enterprise reach.
- Downward pressure: threat telemetry is quiet. EPSS is very low and there is no KEV listing or clear campaign evidence.
- Upward pressure: no fix exists and the plugin was removed. That weakens the long-term maintenance story and raises residual risk for sites that keep it installed.
- Upward pressure: stored XSS can still become privilege escalation. On multi-author sites, contributor-to-admin browser compromise is a real path even if it is not a perimeter RCE.
Why not higher?
This is not an unauthenticated internet exploit against the server. The chain needs Contributor access, a vulnerable content path actually in use, and a privileged user to load the payload, so the reachable population and reliability are both substantially lower than HIGH or CRITICAL cases. There is also no strong evidence of active exploitation pressure.
Why not lower?
I would not drop this to LOW because it is stored, not reflected, and it can realistically target editors or admins in normal WordPress review workflows. The fact that the plugin is closed for security issues with no official fix means residual exposure can persist until defenders actively remove or replace it.
What to do — in priority order.
- Remove or replace the plugin — Because there is no official fixed release, the cleanest control is to remove
templatesnext-toolkitentirely or replace it with a maintained alternative. For a MEDIUM verdict there is no mitigation SLA — go straight to the 365-day remediation window, but on internet-facing multi-author CMS estates I would treat this as sooner-than-backlog hygiene because the plugin is already closed upstream. - Restrict contributor creation — Shut off open registration where possible, review who still has Contributor/Author roles, and require SSO/MFA for any content publisher accounts. This directly attacks the most important prerequisite in the chain; for this MEDIUM verdict there is no mitigation SLA — go straight to the 365-day remediation window, but role cleanup should happen opportunistically during the next access review.
- Quarantine contributor-authored content — Require editorial review and avoid rendering contributor drafts in privileged admin sessions until the plugin is gone. This reduces the second key prerequisite: a higher-privileged victim viewing poisoned content.
- Add WAF and content filtering rules — Block obvious script-bearing payloads, dangerous event handlers, and suspicious shortcode inputs on WordPress content submission endpoints. This is only a partial control, but it can trim low-effort weaponization while you work through removal.
- Audit for existing payloads — Search
wp_postsand related metadata for<script,onerror=,onload=,javascript:,svg/onload, and abnormal shortcode attributes in content authored by low-privilege users. Do this before and after plugin removal to catch already-planted stored payloads.
- Updating WordPress core does not fix this; the flaw is in the plugin.
- Relying on perimeter vuln scanners alone will miss the real risk because the exploit path is authenticated and content-workflow dependent.
- Server-side EDR will not reliably catch browser-side XSS abuse against admins; the attack happens in the victim's session, not as obvious malware on disk.
- A generic password reset for admins only is insufficient if low-privilege contributor accounts remain exposed and the plugin is still installed.
Crowdsourced verification payload.
Run this on the target WordPress host or against a mounted filesystem copy. Invoke it as bash verify-cve-2025-22310.sh /var/www/html and use a user that can read the WordPress plugin directory; root is not required unless file permissions are tight.
#!/usr/bin/env bash
# verify-cve-2025-22310.sh
# Detects whether a WordPress instance has TemplatesNext ToolKit installed at a vulnerable version.
# Usage: bash verify-cve-2025-22310.sh /path/to/wordpress
# Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN
set -u
WP_ROOT="${1:-}"
PLUGIN_DIR=""
MAIN_FILE=""
VERSION=""
if [ -z "$WP_ROOT" ]; then
echo "UNKNOWN - missing WordPress root path argument"
exit 2
fi
PLUGIN_DIR="$WP_ROOT/wp-content/plugins/templatesnext-toolkit"
if [ ! -d "$PLUGIN_DIR" ]; then
echo "PATCHED - plugin directory not present: $PLUGIN_DIR"
exit 0
fi
# Try common files for version extraction.
for f in \
"$PLUGIN_DIR/readme.txt" \
"$PLUGIN_DIR/templatesnext-toolkit.php" \
"$PLUGIN_DIR/index.php"; do
if [ -f "$f" ]; then
MAIN_FILE="$f"
break
fi
done
if [ -z "$MAIN_FILE" ]; then
echo "UNKNOWN - plugin directory exists but no readable version source found"
exit 2
fi
extract_version() {
local file="$1"
local v=""
v=$(grep -Eim1 '^[[:space:]]*Stable tag:[[:space:]]*[0-9][0-9A-Za-z._-]*' "$file" 2>/dev/null | sed -E 's/^[^:]+:[[:space:]]*//I')
if [ -n "$v" ]; then
echo "$v"
return 0
fi
v=$(grep -Eim1 '^[[:space:]]*Version:[[:space:]]*[0-9][0-9A-Za-z._-]*' "$file" 2>/dev/null | sed -E 's/^[^:]+:[[:space:]]*//I')
if [ -n "$v" ]; then
echo "$v"
return 0
fi
return 1
}
VERSION=$(extract_version "$MAIN_FILE") || true
if [ -z "$VERSION" ] && [ -f "$PLUGIN_DIR/templatesnext-toolkit.php" ]; then
VERSION=$(extract_version "$PLUGIN_DIR/templatesnext-toolkit.php") || true
fi
if [ -z "$VERSION" ] && [ -f "$PLUGIN_DIR/readme.txt" ]; then
VERSION=$(extract_version "$PLUGIN_DIR/readme.txt") || true
fi
if [ -z "$VERSION" ]; then
echo "UNKNOWN - could not parse plugin version from $PLUGIN_DIR"
exit 2
fi
# Normalize: keep leading numeric dotted component only.
VERSION=$(echo "$VERSION" | sed -E 's/^([0-9]+(\.[0-9]+){0,3}).*$/\1/')
if [ -z "$VERSION" ]; then
echo "UNKNOWN - parsed version is empty"
exit 2
fi
if printf '%s\n%s\n' "$VERSION" '3.2.9' | sort -V -C; then
echo "VULNERABLE - TemplatesNext ToolKit version $VERSION detected (affected: <= 3.2.9)"
exit 1
else
echo "PATCHED - TemplatesNext ToolKit version $VERSION detected (> 3.2.9)"
exit 0
fi
If you remember one thing.
templatesnext-toolkit, especially sites with Contributor/Author workflows or open registration. Because this is MEDIUM, there is no noisgate mitigation SLA — go straight to the 365-day remediation window; in practice, since the plugin is closed and has no official fix, schedule removal/replacement rather than waiting for a vendor patch, and complete that by the noisgate remediation SLA of ≤ 365 days. If any site is internet-facing and allows low-privileged content authors, accelerate access review and content audit immediately even though this does not rise to emergency patch status.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.