This is graffiti inside the staff hallway, not a crowbar on the front door
CVE-2025-22323 is a stored XSS in the WordPress plugin Image Hover Effects for Elementor (image-hover-effects-elementor-addon). Public sources disagree slightly on the upper bound: Wordfence/WPScan track it as affecting <= 1.0.2.3, while Patchstack and the current NVD record say <= 1.0.2.4. The practical attack is the same either way: a user with Contributor-level or higher access can save malicious content that later executes in another user's browser when the poisoned page is viewed.
The vendor's MEDIUM 6.5 is technically defensible in a lab, but too high for enterprise patch triage. The decisive reality is attacker position: this is not unauthenticated internet RCE, it is post-auth CMS abuse in a plugin with only 50+ active installs and no credible exploitation signal. That combination pushes this into LOW for most defenders, with attention only if you actually run the plugin on multi-author WordPress sites.
4 steps from start to impact.
Land a contributor account
wp-login.php, or Burp Suite for session replay; the vulnerability itself does not provide initial access.- WordPress site is running
image-hover-effects-elementor-addonin an affected version - Attacker has authenticated access with Contributor+ privileges
- The site allows multiple authors or delegated content editing
- Most enterprise WordPress estates do not give contributor rights broadly
- SSO, MFA, and restricted authoring workflows raise the bar before the bug is even reachable
- The plugin's install base is tiny, so the exposed population is inherently small
Store a malicious payload in plugin-controlled content
- The vulnerable field is reachable to the contributor role
- Input validation is not stripping or neutralizing script-capable payloads
- The attacker still needs the right content workflow to place the payload where it will be rendered
- Editorial review or draft-only workflows can prevent the payload from reaching useful viewers
Wait for a victim to render the poisoned page
- A victim user visits the injected page or preview
- Browser-side defenses do not block the payload
- The payload is rendered in a script-executable context
- If only anonymous visitors see the page, the blast radius is much lower
- Well-configured CSP can blunt some payload classes, though many WordPress deployments lack strict CSP
- The attacker may need social or editorial timing to get an admin to view it
Abuse the browser context for follow-on actions
- The victim holds meaningful WordPress privileges
- The script can reach the required admin pages, nonces, or actions
- No server-side execution is inherent to this bug
- Modern EDR will not see much because execution stays in the browser and CMS layer
- The compromise does not naturally jump across unrelated hosts in your fleet
The supporting signals.
| In-the-wild status | No active exploitation evidence found in public sources reviewed, and not listed in CISA KEV. |
|---|---|
| Proof-of-concept availability | No public PoC repo or Metasploit module located; coverage is advisory-level from Patchstack, Wordfence, WPScan, and NVD. |
| EPSS | 0.00254 from the user-supplied intel, which is extremely low and consistent with a low-priority exploitation signal. |
| KEV status | Not KEV-listed. That matters because this bug has none of the current-field pressure signals that should override its post-auth friction. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L — the important parts are PR:L and UI:R: attacker already has an account, and someone still has to render the payload. |
| Affected versions | There is a source discrepancy: Wordfence/WPScan say <= 1.0.2.3; Patchstack and the current NVD record say <= 1.0.2.4. For triage, assume <= 1.0.2.4 is affected. |
| Fixed version | Patchstack's current entry says no official patch available. The WordPress plugin directory currently shows 1.1.0, but there is no explicit vendor security statement tying that release to this CVE, so treat fix status as uncertain unless you validate code changes. |
| Exposure population | The WordPress plugin directory shows only 50+ active installations. That is tiny compared with the plugin populations that usually justify emergency patch motion. |
| Scanner / exposure reality | This is not directly internet-fingerprintable like an appliance CVE. Coverage is mainly version/inventory based through WordPress-aware scanners or filesystem inspection, not Shodan-style external scanning. |
| Disclosure / researcher | Research credited to Gab. Patchstack shows the report on 2024-10-10 and public publication on 2025-01-03; NVD lists the CVE publication date as 2025-01-07. |
noisgate verdict.
The biggest downward driver is attacker position: exploitation requires an already-authenticated Contributor+ user on a niche WordPress plugin, which makes this a post-initial-access content abuse issue, not an external foothold. With 50+ installs, no KEV, and very low EPSS, the reachable population and near-term exploitation pressure are both weak.
Why this verdict
- Post-auth requirement cuts hard: the vendor's 6.5 assumes network reachability, but
PR:Lmeans the attacker already has WordPress access. In enterprise terms that implies prior compromise, delegated author access, or a weak IAM model — all of which materially narrow exposure. - UI:R is real friction: this is stored XSS, but someone still has to view the poisoned content for it to matter. If the payload never reaches an admin/editor eyeball, impact stays limited and often noisy rather than catastrophic.
- Tiny install base drops the fleet priority: the plugin directory shows only 50+ active installs. That is nowhere near the exposure density that justifies treating a medium WordPress XSS like a broad emergency.
Why not higher?
There is no evidence of active exploitation, no public PoC traction, no KEV listing, and no unauthenticated path. This is also site-local browser impact, not server-side RCE or a fleet-spreading primitive. Even on an affected host, multiple real-world prerequisites must line up before the impact becomes meaningful.
Why not lower?
Stored XSS in a CMS is still operationally relevant because it can target higher-privileged admins or editors who review content. If your WordPress sites use external contributors, agencies, marketing contractors, or shared authoring workflows, this can become a practical privilege-pivot on that specific site.
What to do — in priority order.
- Restrict Contributor access — Remove or tightly scope Contributor+ roles on sites running this plugin, especially for contractor or agency accounts. For a LOW verdict there is no SLA beyond backlog hygiene, but do this in the next routine IAM review because it directly removes the exploit prerequisite.
- Disable or remove the plugin where unused — If the widget is not business-critical, uninstalling or disabling it collapses the attack surface entirely. For LOW, handle during normal maintenance rather than breaking change windows.
- Review recently edited content by low-privileged authors — Inspect drafts, pages, and plugin-rendered content created by Contributors or Editors for suspicious HTML/JS payloads. This is backlog-hygiene work for LOW, but it is the fastest way to catch abuse if you suspect a compromised author account.
- Enforce MFA and SSO on WordPress author accounts — MFA will not neutralize the XSS itself, but it raises the cost of obtaining the required Contributor account. For LOW, fold this into your standing WordPress hardening program rather than emergency change control.
- Consider a strict CSP on admin and front-end pages — A well-tuned Content Security Policy can reduce the reliability of many XSS payloads, especially inline script execution. This is not a same-day mitigation for most WordPress stacks, but it is a high-value hardening move during regular backlog work.
- A perimeter WAF alone is not enough, because the malicious payload is often submitted through valid authenticated CMS workflows and later executed from stored content.
- Network segmentation does not solve this; the exploit lives in the WordPress application and browser trust boundary, not east-west host reachability.
- MFA after compromise does not stop an attacker who already has a valid Contributor session; it only helps with the account-acquisition prerequisite.
Crowdsourced verification payload.
Run this on the target WordPress host with read access to the web root. Invoke it as bash check_cve_2025_22323.sh /var/www/html (adjust the WordPress path); root is not required unless your web files are unreadable to the current user.
#!/usr/bin/env bash
# check_cve_2025_22323.sh
# Determine whether a WordPress instance is affected by CVE-2025-22323
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -u
WP_ROOT="${1:-}"
PLUGIN_SLUG="image-hover-effects-elementor-addon"
AFFECTED_MAX="1.0.2.4"
if [[ -z "$WP_ROOT" ]]; then
echo "UNKNOWN - usage: $0 /path/to/wordpress"
exit 2
fi
PLUGIN_DIR="$WP_ROOT/wp-content/plugins/$PLUGIN_SLUG"
if [[ ! -d "$PLUGIN_DIR" ]]; then
echo "UNKNOWN - plugin directory not found: $PLUGIN_DIR"
exit 2
fi
find_main_file() {
local candidates=(
"$PLUGIN_DIR/${PLUGIN_SLUG}.php"
"$PLUGIN_DIR/image-hover-effects-elementor-addon.php"
"$PLUGIN_DIR/plugin.php"
)
for f in "${candidates[@]}"; do
if [[ -f "$f" ]]; then
echo "$f"
return 0
fi
done
local first_php
first_php=$(find "$PLUGIN_DIR" -maxdepth 1 -type f -name '*.php' | head -n 1)
if [[ -n "$first_php" ]]; then
echo "$first_php"
return 0
fi
return 1
}
version_le() {
# returns 0 if $1 <= $2
[[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" == "$1" ]]
}
MAIN_FILE=$(find_main_file) || {
echo "UNKNOWN - could not locate plugin main PHP file under $PLUGIN_DIR"
exit 2
}
VERSION=$(grep -Eim1 '^\s*Version:\s*' "$MAIN_FILE" | sed -E 's/^\s*Version:\s*//I' | tr -d '\r')
if [[ -z "$VERSION" ]]; then
echo "UNKNOWN - could not parse plugin version from $MAIN_FILE"
exit 2
fi
if version_le "$VERSION" "$AFFECTED_MAX"; then
echo "VULNERABLE - $PLUGIN_SLUG version $VERSION is within published affected range <= $AFFECTED_MAX"
exit 1
else
echo "PATCHED - $PLUGIN_SLUG version $VERSION is outside the published affected range <= $AFFECTED_MAX"
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.