WordPress插件MStoreAPI存在认证绕过漏洞(POC已公开)CVE-2026-13447

admin 2026-09-25 05:14:23 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: WordPress插件MStoreAPI存在认证绕过漏洞CVE-2026-13447,影响版本≤4.20.0,修复版本4.21.1。漏洞源于JWT伪造,未校验签名,可冒用任意手机号登录已有账号或创建新账号,CVSS评分9.8。建议立即升级至4.21.1或更高版本。 综合评分: 85 文章分类: 漏洞分析,代码审计,应急响应


WordPress插件MStore API存在认证绕过漏洞(POC已公开)CVE-2026-13447

YGnight YGnight

night安全

2026年9月22日 08:00 四川

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

一、漏洞描述

MStore API 是 inspireui 给 WordPress 做的插件,把站点里的商品、用户、订单接到 FluxBuilder,用来生成配套的手机 App。WordPress 官方目录显示活跃安装两千以上。

出事的是短信登录。

插件在 /wp-json/api/flutter_user 下注册了 firebase_sms 与 firebase_sms_v2 两条路径,都接收 POST 过来的 id_token。

整个请求过程不要求 WordPress 账号。

路由的权限回调指向 checkApiPermission,内部只剩一句 return true,原先读取采购码并比对哈希的那几行被注释保留着。

令牌校验集中在 FirebasePhoneAuthHelper 的 verify_id_token,只取前两段,比对 alg、kid、aud、iss 四项。

openssl_verify 之类的验签调用不在这条路径上,用自建 RSA 密钥签发的一条令牌照样被接受。

CVE 编号 CVE-2026-13447,归类 CWE-287。CVSS 3.1 打分 9.8,由 CVE 记录中的 CNA 提交,NVD 一栏没有给出自己的分值。

受影响区间记为 ≤ 4.20.0。官方 SVN 里 4.18.4 之后直接是 4.21.1,修复落在 4.21.1。

公开的验证脚本只连本地 127.0.0.1,容器编排文件随复现仓库一起给出,命中标记是响应体里出现目标账号的显示名。

| | | | — | — | | CVE 编号 | CVE-2026-13447 | | 公告编号 | GHSA-6wfp-pwm3-667v | | 漏洞类型 | JWT 伪造导致的认证绕过,CWE-287 | | 影响组件 | WordPress 插件 MStore API 的 FirebasePhoneAuthHelper,firebase_sms 与 firebase_sms_v2 两条 REST 路径 | | 受影响版本 | ≤ 4.20.0 | | 修复版本 | 4.21.1 及更高 | | 利用条件 | 无需认证,无需用户交互,需要预先知道站点配置的 Firebase project_id | | 危害面 | 冒用任意手机号登录已有账号并取回登录 Cookie,firebase_sms 一侧另可建新账号 | | CVSS | 9.8,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,NVD 未给出自有分值 |

二、漏洞原理

firebase_sms_v2 的 permission_callback 指向 checkApiPermission,这条路最终落到 isPurchaseCodeVerified。

// controllers/flutter-user.php 4.18.4 register_rest_route($this->namespace, ‘/firebase_sms_v2’, array(     array(         ‘methods’ => ‘POST’,         ‘callback’ => function ($request) {             $phone = $this->firebase_sms_verify_id_token($request);             if (is_wp_error($phone)) { return $phone; }             return $this->firebase_sms_login_v2($phone);         },         ‘permission_callback’ => function () {             return parent::checkApiPermission();         }     ), ));

// controllers/flutter-base.php 4.18.4 public function checkApiPermission() {     return isPurchaseCodeVerified(); }

// functions/index.php 4.18.4 function isPurchaseCodeVerified(){     return true;     // $random_key = get_option(‘mstore_active_random_key’);     // $hash_code = get_option(‘mstore_active_hash_code’);     // return md5(‘inspire@123%$’.$random_key) == $hash_code && strlen($code) > 0; }

函数体第一行就是 return true,授权码比对的那段代码被留在注释里。

请求体由 firebase_sms_verify_id_token 从 php://input 读出,交给 verify_id_token。

// controllers/helpers/firebase-phone-auth-helper.php 4.18.4 public function verify_id_token($id_token){     $splitToken = explode(“.”, $id_token);     $headerBase64 = $splitToken[0];     $decodedHeader = json_decode(urldecode(base64_decode($headerBase64)), true);

    if (!isset($decodedHeader[‘alg’]) || $decodedHeader[‘alg’] != ‘RS256’) {         return false;     }

    $public_keys = $this->get_public_keys();     if (!isset($decodedHeader[‘kid’]) || !in_array($decodedHeader[‘kid’], $public_keys)) {         return false;     }

    $payloadBase64 = $splitToken[1];     $decodedPayload = json_decode(urldecode(base64_decode($payloadBase64)), true);     $projectId = $json[‘project_id’];

    if (!isset($decodedPayload[‘aud’]) || $decodedPayload[‘aud’] !== $projectId) {         return false;     }

    if (!isset($decodedPayload[‘iss’]) || $decodedPayload[‘iss’] !== ‘https://securetoken.google.com/’.$projectId) {         return false;     }

    return isset($decodedPayload[‘phone_number’]) ? trim($decodedPayload[‘phone_number’]) : false;     // $splitToken[2] 全文没有再出现过 }

// 公开验证脚本构造的令牌,第三段是 base64 编码的字母 x id_token = eyJhbGciOiJSUzI1NiIsImtpZCI6… . eyJhdWQiOi… . eA== // 前两段凑齐 kid、aud、iss、phone_number 四处取值即可

explode 按点号把令牌切成三段,下标 0 和 1 之后就没人再往下取。

get_public_keys 拉 Google 公布的 x509 元数据,返回 array_keys 的结果,证书内容只被当成一串 kid 字符串比对。

project_id 读自站点管理员上传的那份 Firebase 配置,aud 与 iss 要跟它一致,这两项需要预先知道。

四项都过,函数就把那个手机号交回去,第三段的内容不影响结果。

拿到手机号后,firebase_sms_login_v2 去元信息 registered_phone_number 查人,命中了就签发登录 Cookie。

// controllers/flutter-user.php 4.18.4 firebase_sms_login_v2 片段 $args = array(‘meta_key’ => ‘registered_phone_number’, ‘meta_value’ => $phone); $search_users = get_users($args); if (empty($search_users)) {     $domain = $_SERVER[‘SERVER_NAME’];     $user = get_user_by(’email’, $phone . “@” . $domain);     if (!$user) {         return parent::sendError(“invalid_login”, “User does not exist”, 400);     } } $user = $search_users[0]; $cookie = generateCookieByUserId($user->ID); $response[‘cookie’] = $cookie; $response[‘user’] = $this->getResponseUserInfo($user);

// functions/index.php 4.18.4 function generateCookieByUserId($user_id, $seconds = 1209600){     $expiration = time() + 365 * DAY_IN_SECONDS;     $cookie = wp_generate_auth_cookie($user_id, $expiration, ‘logged_in’);     return $cookie; }

第二个参数收进来就放着没用,有效期在下一行写死成 365 天,发出去的是 WordPress 登录态 Cookie。

另一条路径 firebase_sms 落在 firebase_sms_login,最后走到 createSocialAccount,手机号拼一个邮箱交给 wp_insert_user。

userdata 数组里不带 role 键,新建账号继承站点默认角色。

4.21.1 换成了 firebase/php-jwt 的 JWT::decode。

// controllers/helpers/firebase-phone-auth-helper.php 4.21.1 $key_objects = []; foreach ($public_keys as $kid => $key) {     $key_objects[$kid] = new Key($key, ‘RS256’); }

$decoded_token = JWT::decode($id_token, $key_objects);

if ($decoded_token->aud !== $actual_project_id) {     return new WP_Error(‘firebase_auth_error’, ‘Invalid token audience.’, array(‘status’ => 401)); } if (!isset($decoded_token->auth_time) || $decoded_token->auth_time > time()) {     return new WP_Error(‘firebase_auth_error’, ‘Invalid auth_time.’, array(‘status’ => 401)); } return trim($decoded_token->phone_number);

公钥按 kid 组装成 Key 对象交给库,验签连同过期时间与生效时间一起交给 JWT::decode 处理。

补丁里还留了一段注释。旧版本的 urldecode 先把号码里的加号转成空格,随后 trim 又把首尾清掉,老版本建的账号只剩纯数字。

新版按标准 base64url 解码,加号保留下来,查号时带加号和不带加号两种写法各试一遍,回头客不会再被当成新号。

三、修复建议

MStore API 升到 4.21.1 或更高

近期值得重点关注的漏洞预警

【紧急漏洞预警】nacos爆出权限绕过漏洞,可以创建管理员账户,经过分析有点出处(已复现,exp脚本已编写)

CakePHP爆出SQL注入漏洞(POC已公开)CVE-2026-77635

Node.js模块proxy-addr爆出严重漏洞CVE-2026-90711

Langflow爆出代码执行漏洞CVE-2026-17633  (POC已公开)

Apache CVE-2026-42536,补它只用一行,洞龄十四年

【紧急】搜狗输入法爆出远程代码执行漏洞CVE-2026-51990  ,已被在野利用

【紧急】无需登录就能获取 GitLab 的任意文件数据(附POC)

【紧急】JumpServer堡垒机被公布Access Key泄露漏洞,可以实现提权

Spring Cloud Azure公布严重漏洞 无需任何权限即可取得登录会话

【漏洞POC已公开】Linux RDS 爆出提权漏洞 一块内存放两次,root就到手了

Tenda光猫爆出严重漏洞,目前无补丁(POC已公布

13 行补丁,堵住Linux能拿 root 的内核洞(EXP已公开)

48 字节,换走一台 Windows 的 SYSTEM(CVE-2026-62735漏洞POC已公开)

【漏洞预警】linux删个网桥就能提权root?目前没有补丁(POC已公开)

【漏洞预警】Linux三年前补上的洞,又一次被挖开了

【情报】支付宝8.2亿数据泄露?给同事说一声,别自己吓自己

【漏洞预警】自己注册个号,就能搬空别人的 Magento 订单和支付卡(POC已公开)

【漏洞预警】Next.js 爆出新漏洞,不用登录实现命令执行(POC已公开)

【漏洞补丁绕过预警】Redis CVE-2026-23479变种高危漏洞,EXP已公开

【漏洞补丁绕过】超 30 万 WordPress 站点受影响,Forminator 文件上传漏洞(附POC)

【漏洞预警】Exchange爆出RCE漏洞,利用Exchange 帮另一台 Exchange 开门(POC已公开)

【漏洞预警】不用登录,一个请求就能让 GeoServer 乖乖执行命令(POC已公开)

【漏洞预警】Keycloak不登录就能接管任意账户,双重验证也拦不住

【漏洞预警】 8月WebLogic公布了9个漏洞,有一个需要重点关注

【漏洞预警】近期MongoDB 公布了5个漏洞,最严重的一个可以不认证、远程就能拿下你电脑

【0DAY漏洞】 全球5G手机芯片厂商之一‌的紫光展锐SOC存在RCE漏洞,影响数十家手机厂商,还未发布补丁

【漏洞告警】安卓 14 到 16 全中招,通讯录 SQL 注入漏洞正在抄家(POC已公开)

【漏洞预警】紧急排查,SMB服务再曝新漏洞,利用脚本已公开

【漏洞预警】不用输密码,对方已经接管你的Mac:官方补丁公布macOS存在未鉴权RCE漏洞

【0day漏洞预警】MariaDB 13 爆 9.8 分 RCE 链:最低 USAGE 权限就能远程执行命令

VMware 爆 5 个洞,两个 9.8 不用密码就能打穿 vCente


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。

本文转载自:night安全 YGnight YGnight《WordPress插件MStore API存在认证绕过漏洞(POC已公开)CVE-2026-13447》

评论:0   参与:  0