已认证的投稿者(Contributor)可以在 WordPress Strong Testimonials 插件中存储由攻击者控制的平台照片元数据。在 3.3.12 版本之前,该插件会将存储的值渲染到图片的 src 属性中,且未进行上下文转义。在管理员发布精心构造的推荐语以及展示该推荐语的帖子后,载荷会针对打开该帖子的访客执行,包括未认证的访客。
本研究在授权的本地实验环境中进行,并在公开发布前通过 Patchstack 披露。
| 字段 | 值 |
|---|---|
| CVE | CVE-2026-97286 |
| 产品 | Strong Testimonials |
| 供应商 | WP Chill |
| 插件 slug | strong-testimonials |
| 漏洞 | 已认证存储型跨站脚本 |
| 受影响版本 | <= 3.3.11 |
| 修复版本 | 3.3.12 |
| 所需权限 | 投稿者或更高权限 |
| CWE | CWE-79:网页生成期间输入中和不当 |
| CVSS v3.1 | 6.5 中危 — CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L |
| 研究员 | Rully Miftahur Rozaq |
| 报告日期 | 2026 年 9 月 16 日 |
| 公开披露日期 | 2026 年 9 月 30 日 |
尽管分配的 CVSS v3.1 基础严重性为“中危”,Patchstack 仍将该公告归类为低优先级。
推荐语编辑器接受 custom 请求数组,并将每个提交的键持久化为文章元数据。未出现在已配置自定义字段定义中的键会落入 sanitize_text_field() 处理。这允许投稿者在编辑自己拥有的推荐语时提交插件保留的元数据,例如 platform 和 platform_user_photo。
对于受支持的社交平台,插件会检索 platform_user_photo,验证该 URL 返回 HTTP 200 响应,然后将原始存储字符串插入到 <img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/..."> 属性中。HTTP 状态验证并不能使字符串安全地用于 HTML 属性。URL 片段中的引号可以终止 src 并引入事件处理器。
已验证的攻击链如下:
该注入需要认证。触发已发布载荷的访客无需认证。
成功利用可在受影响的 WordPress 源中,在通过易受攻击的缩略图路径渲染精心构造推荐语的页面上实现持久性 JavaScript 执行。代码以访客的浏览器权限和 WordPress 会话(如有)执行。
复现路径需要管理员发布,这对应 CVSS 的 UI:R 指标。本研究未演示未认证注入、在所有站点页面上自动执行、权限提升、Cookie 窃取或完整站点沦陷。
概念验证在以下配置下复现:
| 组件 | 配置 |
|---|---|
| WordPress | 7.1 |
| Strong Testimonials | 3.3.7,免费版 |
| 主题 | Twenty Twenty-Five |
| 显示模板 | default-theme |
| 缩略图 | 已启用 |
| PHP / Apache | PHP 8.3.33 / Apache 2.4.68 |
| 数据库 | MariaDB 11.4 |
| 攻击者角色 | 投稿者 |
| 攻击者能力 | edit_posts=true、publish_posts=false、unfiltered_html=false |
3.3.7 版本是实验室中使用的确切版本。公开 CVE 记录将 3.3.11 及之前的所有版本标识为受影响,并将 3.3.12 版本标识为已修复。
在 admin/class-strong-testimonials-post-editor.php 中,save_details() 会遍历提交的 custom 数组中的每一个条目。无法识别的键会进入通用清理分支,随后通过 update_post_meta() 存储:
$custom = $_POST['custom'];
foreach ( $custom as $key => $value ) {
// ...
$sanitized_value = sanitize_text_field( $value );
update_post_meta( $post_id, $key, $sanitized_value );
}
真正的处理程序会执行 nonce 校验和针对特定字段的清理。该发现并不依赖于绕过这些校验或编辑其他用户的推荐语。Contributor 合法地编辑自己的推荐语,但提供了保留键 platform 和 platform_user_photo。
在存在漏洞的版本中,includes/functions-image.php 会读取 platform_user_photo,通过 wpmtst_is_valid_image_url() 执行 HTTP 状态检查,并将原始字符串插入到 HTML 属性中:
$platform_user_photo = get_post_meta( $post_id, 'platform_user_photo', true );
if ( ! $platform_user_photo || ! wpmtst_is_valid_image_url( $platform_user_photo ) ) {
return $img;
}
return sprintf(
'<img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/%25s" %s %s/>',
$platform_user_photo,
$width ? "width='{$width}'" : '',
$height ? "height='{$height}'" : ''
);
URL 的片段部分不会发送到 HTTP 服务器,但它仍保留在随后由 PHP 渲染的字符串中。因此,服务器可以看到一个返回 HTTP 200 的无害 URL,而浏览器却收到一个包含属性突破序列的值。
default-theme 模板调用 wpmtst_get_thumbnail() 并直接输出返回的图像标记:
<figure class="wp-block-image wpmtst-testimonial-avatar">
<?php echo $wpmtst_avatar_html; ?>
</figure>
该模板假定辅助程序已经生成了安全的 HTML。在受影响的版本中,平台照片辅助程序未对其 src 上下文中的 URL 进行转义。
仅在你拥有或获得明确授权测试的系统上复现此问题。
<= 3.3.11。default-theme 且启用了缩略图的 Strong Testimonials Display 视图。以 Contributor 身份登录并创建一条新的 testimonial 草稿。
使用拦截代理捕获合法的 POST /wp-admin/post.php 保存请求。保留其完整的表单正文、会话 cookie、新的 WordPress nonce、插件 nonce,以及该 Contributor 拥有的 testimonial 的 ID。
将以下参数追加到表单正文中:
custom[platform]=facebook
custom[platform_user_photo]=http://wordpress/index.php?rest_route=/#" onerror="alert(1)" x="
URL 编码的表单片段:
&custom%5Bplatform%5D=facebook&custom%5Bplatform_user_photo%5D=http%3A%2F%2Fwordpress%2Findex.php%3Frest_route%3D%2F%23%22%20onerror%3D%22alert%281%29%22%20x%3D%22
wordpress 主机名是本地实验环境中使用的 Docker 服务名。在其他经授权的环境中,请将其替换为研究人员控制的测试端点,该端点需可被 WordPress 访问并返回 HTTP 200。不要以第三方或敏感的内部服务为目标。
发送修改后的保存请求并提交该推荐语以供审核。
创建一个包含 Shortcode 区块的普通 WordPress 文章。将示例 ID 替换为测试安装中的 Display 视图 ID 和构造的推荐语 ID:
[testimonial_view id="2" post_ids="22" thumbnail="1"]

提交文章以供审核。以管理员身份,发布精心构造的推荐语和触发文章。
在未登录 WordPress 的独立浏览器或隐私窗口中打开已发布的触发文章。
已直接从文章元数据中验证存储的值:

前端响应中包含一个等效于以下内容的图像元素:
<img src="http://wordpress/index.php?rest_route=/" onerror="alert(1)" x="" width="100" height="100">
随后,该载荷在未认证访客访问时被执行:

一个不可重放、已脱敏的请求模板位于 poc/request-template.txt。该模板有意省略了可复用的认证信息和 nonce 值。
Strong Testimonials 3.3.12 在存在漏洞的接收点处应用了上下文相关的 URL 转义:
-return sprintf( '<img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/%25s" %s %s/>', $platform_user_photo, $width ? "width='{$width}'" : '', $height ? "height='{$height}'" : '' );
+return sprintf( '<img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/%25s" %s %s/>', esc_url( $platform_user_photo ), $width ? "width='{$width}'" : '', $height ? "height='{$height}'" : '' );
该修复可在 3.3.12 的已标记源码中查看。存在漏洞的实现仍可在 3.3.11 中看到。
站点所有者应将 Strong Testimonials 更新至 3.3.12 或更高版本。
为实现纵深防御,维护者还应:
custom 键。esc_url_raw() 或等效的 URL 清理器验证允许的 URL 字段。esc_url(),对普通属性使用 esc_attr()。由 Rully Miftahur Rozaq 发现并报告。
感谢 Patchstack 和 WP Chill 协调披露与修复工作。