[webapps] WordPress Plugin WPZOOM Portfolio 1.4.21 - Reflected Cross-Site Scripting (XSS)
CVE-2026-49069是WordPress WPZOOM Portfolio插件1.4.21及之前版本中的反射型XSS漏洞,源于未认证AJAX动作wpzoom_load_more_items缺少nonce与权限验证,且用户输入经sanitize_text_field后仍含单引号,被直接拼入HTML属性。攻击者可注入onmouseover等事件处理器,在受害者浏览器执行任意脚本,造成会话劫持、页面篡改等危害。本文分析漏洞原理、PoC利用步骤及加固建议。
WPZOOM Portfolio插件未认证AJAX接口存在反射型XSS,可注入恶意事件处理器。
High · CVSS 7.1📋 漏洞基础信息
| CVE | CVE-2026-49069 |
|---|---|
| 漏洞类型 | 跨站脚本(XSS)- Reflected Cross-Site Scripting |
| 受影响版本 | WordPress Plugin WPZOOM Portfolio <= 1.4.21 |
| 危害等级 | High · CVSS 7.1 |
| 发布日期 | 2026-07-06 |
| 提交者 | Kent Apostol |
| 来源 | Exploit-DB 原文 ↗ |
🔬 漏洞根因
插件通过wp_ajax_nopriv_wpzoom_load_more_items注册了未认证的AJAX处理器,未验证nonce和权限。处理过程中读取用户可控的posts_data参数,经sanitize_text_field()过滤后JSON解码,再通过wp_parse_args()合并到$args。其中class键被直接拼接到items_html()的多个HTML属性中(如<li class='{$class}_item'>),未使用esc_attr()等上下文感知转义,且sanitize_text_field()保留单引号,导致攻击者可突破属性边界注入事件处理器。
🎯 攻击场景
1. 攻击者构造恶意POST请求发送到/wp-admin/admin-ajax.php,其中action=wpzoom_load_more_items,posts_data中包含class字段,值为'x' onmouseover='alert(document.domain)' y=';2. 服务器端查询返回至少一个已发布的portfolio项,并将上述class值拼接到HTML属性中;3. 攻击者诱导受害者访问包含该输出的页面,或通过其他方式使受害者在浏览器中加载该响应;4. 受害者将鼠标悬停在加载的portfolio项目上,触发onmouseover事件,执行任意JavaScript代码。
💥 漏洞影响
攻击者可执行任意JavaScript代码,窃取会话Cookie、劫持管理员会话、篡改页面内容、发起钓鱼攻击,或在受害用户浏览器中执行其他恶意操作。由于无需认证,攻击门槛低,风险较高。
⚔️ 原始 PoC
PoC中发送POST请求,关键参数为posts_data={"source":"post","class":"x' onmouseover='alert(document.domain)' y='"}。原理解析:1. 该AJAX动作允许未认证访问;2. 服务端将posts_data解码后,class值中的单引号闭合了原有class属性,使onmouseover成为新属性;3. 响应中显示<li class='x' onmouseover='alert(document.domain)' y='_item ...'>,证明注入成功;4. 浏览器渲染后,悬停即触发脚本。
# Exploit Author: Kent Apostol
Vulnerability Description:
The WPZOOM Portfolio plugin for WordPress is vulnerable to unauthenticated reflected Cross-Site Scripting via the `wpzoom_load_more_items` AJAX action. The handler is registered for unauthenticated users through `wp_ajax_nopriv_wpzoom_load_more_items` and performs no nonce validation or privilege checks.
The application reads the `posts_data` POST parameter,
passes it through `sanitize_text_field()`,and JSON-decodes it into an array which is then merged into `$args` via `wp_parse_args()`. The attacker-controlled `class` key from this array is assigned to `$args['class']` and concatenated directly into multiple single-quoted HTML attributes within `items_html()` (e.g.,`<li class='{$class}_item ...'>`,
`<article class='{$class}_item-wrap ...'>`,`<h3 class='{$class}_item-title'>`) without any context-aware output escaping like `esc_attr()`.
Because `sanitize_text_field()` strips angle brackets (`<`,`>`) but preserves single quotes (`'`),an attacker can break out of the HTML attribute string and inject arbitrary attributes,
including malicious JavaScript event handlers. The payload executes when the underlying query returns at least one published portfolio item.
Proof of Concept (PoC):
Step 1: As an unauthenticated user,
send the following POST request to the target site:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: TARGET
Content-Type: application/x-www-form-urlencoded
Content-Length: 111
action=wpzoom_load_more_items&offset=0&posts_data={"source":"post","class":"x' onmouseover='alert(document.domain)' y='"}
Step 2: Review the HTTP response to verify that the single quote has successfully broken out of the class attribute structure to inject the event handler:
<li class='x' onmouseover='alert(document.domain)' y='_item ...' data-category='1'>Step 3: When rendered in a victim's browser,triggering the event handler (hovering over the loaded portfolio item) executes the JavaScript code.🔬 深度技术分析
PoC中发送POST请求,关键参数为posts_data={"source":"post","class":"x' onmouseover='alert(document.domain)' y='"}。原理解析:1. 该AJAX动作允许未认证访问;2. 服务端将posts_data解码后,class值中的单引号闭合了原有class属性,使onmouseover成为新属性;3. 响应中显示<li class='x' onmouseover='alert(document.domain)' y='_item ...'>,证明注入成功;4. 浏览器渲染后,悬停即触发脚本。
🛡️ 修复建议
1. 升级WPZOOM Portfolio至最新版本(1.4.21之后的修复版本);2. 强制对所有输出到HTML属性的数据使用esc_attr()转义;3. 在AJAX处理器中增加nonce校验和权限检查;4. 临时缓解措施:通过Web应用防火墙拦截包含posts_data且带on*事件属性的请求,或禁用该AJAX动作。
📎 参考链接
- https://nvd.nist.gov/view/vuln/detail?vulnId=CVE-2026-49069
- https://wordpress.org/plugins/wpzoom-portfolio/
- Exploit-DB 原文
🚨 威胁评估
| 📈 EPSS 利用概率 | 暂无数据 |
| 🚨 CISA KEV | 未被已知利用 |
| 🔧 公开 PoC | 暂无公开 PoC |
⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-08-09 08:40 | 来源: Exploit-DB
🤖 常见问题解答(FAQ)
❓ 该漏洞是否需要用户交互?
需要。攻击者必须先诱导受害者浏览触发AJAX请求并渲染结果的页面,然后受害者悬停到注入元素上才会执行恶意脚本。
❓ sanitize_text_field为何无法防御此攻击?
sanitize_text_field会去除尖括号和多余空格,但保留单引号,因此无法阻止攻击者使用单引号闭合HTML属性来注入新属性。
❓ 攻击前置条件是什么?
目标站点需安装受影响版本插件,且存在至少一个已发布的portfolio项目,以便后台查询返回数据并输出注入的HTML。