CVE-2026-XXXXXX — Remote Code Execution w Dolibarr ERP/CRM (sandbox bypass w dol_eval_standard)
Niekompletny sandbox w funkcji dol_eval_standard() pozwala uwierzytelnionemu administratorowi Dolibarr na zdalne wykonanie kodu przez złośliwy computed extrafield — pełny łańcuch eval-to-RCE złożony z trzech niezależnych obejść filtra. CVSS 3.1: 9.1 (CRITICAL).
CVE-2026-XXXXXX — RCE przez obejście sandboxa w dol_eval_standard() (Dolibarr ERP/CRM)
Responsible Disclosure: Podatność zgłoszona do zespołu bezpieczeństwa Dolibarr poprzez GitHub Security Advisory (GHSA-5cfw-655w-vqp8), zaakceptowana przez producenta i naprawiona w wersji 23.0.3 (commit ee8ded7) przed publikacją niniejszego artykułu. Researcher: Grzegorz Tworek (Sec4check). Jeśli korzystasz z Dolibarr — zaktualizuj instancję do 23.0.3 lub nowszej.
Podsumowanie
Podczas niezależnych badań bezpieczeństwa zidentyfikowałem podatność typu Remote Code Execution w Dolibarr ERP/CRM. Problem leży w niekompletnym sandboxie funkcji dol_eval_standard() (core/lib/functions.lib.php), która ewaluuje wyrażenia tzw. computed extrafields — pól wyliczanych definiowanych przez administratora. Uwierzytelniony administrator może utworzyć złośliwe wyrażenie, które omija filtr bezpieczeństwa i prowadzi do wykonania dowolnego kodu PHP na serwerze.
Mimo że skonfigurowanie pola wymaga uprawnień administratora, kod wykonuje się w kontekście każdego użytkownika, który otworzy dotknięty rekord. Złośliwy lub przejęty administrator uzyskuje więc trwałe RCE na całym serwerze, z potencjałem lateral movement przez kradzież poświadczeń. Stąd kwalifikacja Scope: Changed i ocena CVSS 3.1: 9.1 (CRITICAL).
| CVE ID | CVE-2026-XXXXXX — w trakcie przydziału (MITRE CNA-LR) |
|---|---|
| GHSA | GHSA-5cfw-655w-vqp8 |
| Typ | CWE-94: Improper Control of Generation of Code (Code Injection) |
| Produkt | Dolibarr ERP/CRM |
| Wersje podatne | ≤ 23.0.0 (potwierdzone na 23.0.0; prawdopodobnie wszystkie z domyślną ścieżką whitelist) |
| Komponent | dol_eval_standard() w core/lib/functions.lib.php |
| Wektor ataku | Sieciowy (uwierzytelniony) |
| Wymagane uprawnienia | Wysokie — konto administratora |
| Interakcja użytkownika | Brak dla samego zapisu; wykonanie następuje przy otwarciu rekordu przez dowolnego użytkownika |
| CVSS 3.1 | 9.1 CRITICAL |
| Wektor CVSS | AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H |
| Status | Naprawione przez producenta w Dolibarr 23.0.3 (commit ee8ded7) |
Analiza techniczna
Dolibarr pozwala definiować computed extrafields — pola, których wartość jest wyliczana z wyrażenia PHP ewaluowanego przez dol_eval() → dol_eval_standard(). Aby ograniczyć ryzyko, funkcja implementuje sandbox oparty na filtrowaniu regexami (ścieżka „whitelist"). Podatność to trzy niezależne obejścia tego sandboxa, które złożone razem dają pełny łańcuch eval-to-RCE.
Bypass 1 — nieograniczona instancjacja klas (linia 12283)
Ścieżka whitelist dopuszcza new ClassName() dla dowolnej klasy, blokując wyłącznie ReflectionFunction:
Pozwala to instancjonować niebezpieczne klasy wbudowane: PDO, SimpleXMLElement, SQLite3, SplFileObject.
Bypass 2 — składnia callable array omija whitelistę funkcji (linia 12271)
Regex whitelisty /([\s\w\'\]\"]+)\(/ wykrywa wywołania funkcji/metod, dopasowując tekst przed (. Jednak składnia callable array ([$object, "method"])() umieszcza ) tuż przed ( — a ) nie należy do klasy znaków [\s\w\'\]\"]. Wywołanie jest dla whitelisty niewidoczne.
Dodatkowo tablica $forbiddenphpstrings zawierająca )( (linia 12195) jest sprawdzana wyłącznie na ścieżce blacklist (wewnątrz if (empty($dolibarr_main_restrict_eval_methods))), a nie na domyślnej ścieżce whitelist. Umożliwia to wywoływanie dowolnych metod na zinstancjonowanych obiektach: ([$pdo, "exec"])("SQL").
Bypass 3 — SQLite char() omija restrykcję znaku „<" (linia 12120)
Sandbox blokuje < poprzedzające znak niebędący białym znakiem, by uniemożliwić budowę tagów HTML/PHP. Funkcja char() SQLite konstruuje ciąg <?php z kodów znaków na poziomie bazy danych, więc literał < nigdy nie pojawia się w ewaluowanym wyrażeniu.
Pełny łańcuch
Trzy obejścia składają się w jedno wyrażenie, które tworzy w webroocie plik bazy SQLite z rozszerzeniem .php, zawierający w swoich bajtach literał <?php system('id');?> (wygenerowany przez char()/hex SQLite). Gdy plik zostanie otwarty przez HTTP, parser PHP znajduje tag <?php i wykonuje osadzony kod:
Blob X'3C3F...3E' koduje <?php system('id');?> i unika nawiasów wewnątrz stringa SQL (które złapałaby whitelista).
Kroki reprodukcji
- Dolibarr 23.0.0, konfiguracja domyślna, konto administratora; katalog
htdocs/custom/zapisywalny dla serwera WWW (domyślnie). - Włącz moduł z obsługą extrafields (np. Third Parties): Home → Setup → Modules/Applications.
- Utwórz computed extrafield z nieszkodliwą wartością (
/societe/admin/societe_extrafields.php→ New attribute): kodrcetest, typ Varchar, Computed value =1 + 1. Zapisz. - Edytuj atrybut i zmień Computed value na payload łańcucha RCE (tworzący
/var/www/html/custom/0.php). Payload przechodzi przez filtrnohtmlGETPOST i trafia do bazy. - Otwórz dowolny rekord Third Party — computed extrafield jest ewaluowany przez
dol_eval()→eval(), co tworzy plik webshella. - Wykonaj:
curl http://TARGET/custom/0.php.
Oczekiwany wynik: uid=33(www-data) gid=33(www-data) groups=33(www-data) — polecenie system('id') wykonuje się jako użytkownik serwera WWW, potwierdzając RCE. Nazwa pliku musi mieć cyfrę tuż przed kropką (np. 0.php), by ominąć kontrolę „kropki między znakami nienumerycznymi".
Wariant — dowolne wykonanie poleceń
Zamiana bloba na <?php system($_GET['c']);?> daje pełny webshell: curl "http://TARGET/custom/0.php?c=whoami".
Ocena wpływu (Impact Assessment)
Remote Code Execution
Wykonanie dowolnego kodu PHP jako użytkownik serwera WWW (www-data/apache) — pełna kontrola nad aplikacją i serwerem.
SSRF
Sam bypass instancjacji klas (new SimpleXMLElement("http://...", 0, true)) wymusza wychodzące żądania do sieci wewnętrznej i metadanych chmury (169.254.169.254).
Arbitrary File Read (XXE)
W połączeniu z atakującym serwerem XML — odczyt dowolnych plików, w tym conf/conf.php z poświadczeniami bazy danych. Bez konieczności zapisu webshella.
Trwałość i eskalacja
Computed field wykonuje się przy każdym otwarciu rekordu przez dowolnego użytkownika — persistent RCE. Przejęte konto admina = pełna kompromitacja serwera.
CVSS 3.1 — szczegółowa punktacja
| Metryka | Wartość |
|---|---|
| Attack Vector | Network — exploitowane przez aplikację webową |
| Attack Complexity | Low — brak specjalnych warunków |
| Privileges Required | High — wymagane konto administratora |
| User Interaction | None — wykonanie przy zwykłym przeglądaniu rekordu |
| Scope | Changed — kod wykonuje się w kontekście innych użytkowników i całego serwera |
| Confidentiality / Integrity / Availability | High / High / High — pełna kompromitacja |
CVSS 3.1 Score: 9.1 (CRITICAL)
Vector: AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
Rekomendowana remediacja
Zastosowane/rekomendowane poprawki:
- Blokować wszystkie niebezpieczne klasy wbudowane na ścieżce whitelist, nie tylko
ReflectionFunction— utrzymywać jawny allowlist bezpiecznych klas (obiekty biznesowe Dolibarr). - Sprawdzać
)(również na ścieżce whitelist — przenieść kontrolę$forbiddenphpstringsprzed rozgałęzienie if/else, by obowiązywała uniwersalnie. - Blokować
char(i inne funkcje SQL/kodujące, które mogą budować niebezpieczne ciągi w runtime. - Rozważyć analizę AST (np.
token_get_all()zdol_eval_new) zamiast filtrowania regexami.
Producent zaadresował problem w Dolibarr 23.0.3 (commit ee8ded7 dla obejść 1 i 3; obejście 2 zostało zablokowane wcześniejszą zmianą).
Oś czasu (Disclosure Timeline)
| Data | Zdarzenie |
|---|---|
| 2026-03-26 | Odkrycie podatności i potwierdzenie PoC |
| 2026-03-28 | Zgłoszenie przez GitHub Security Advisory (GHSA-5cfw-655w-vqp8) |
| 2026-03-29 | Producent zaakceptował zgłoszenie |
| 2026-05-28 | Poprawka wydana (Dolibarr 23.0.3, commit ee8ded7); advisory zamknięte |
| 2026-10-05 | Publikacja artykułu i rejestracja CVE jako third-party (MITRE CNA-LR) |
Wnioski
Ten przypadek pokazuje, dlaczego sandbox oparty na regexach jest kruchy. Filtr blokował pojedyncze, „oczywiste" wzorce (jedna klasa, jeden znak, jeden ciąg), ale pominął całe klasy równoważnych konstrukcji: inne niebezpieczne klasy, alternatywną składnię wywołań i budowanie ciągów po stronie bazy danych. Co gorsza, część kontroli obowiązywała tylko na jednej z dwóch ścieżek (blacklist vs whitelist) — niespójność, która sama w sobie jest obejściem.
- Deny-list nie skaluje się dla eval. Jeśli ewaluujesz wyrażenia, bezpieczny jest tylko jawny allowlist operacji — nie czarna lista „złych" wzorców.
- Spójność ścieżek. Kontrola egzekwowana na jednej gałęzi, a pominięta na drugiej, to klasyczne źródło bypassów.
- Składnia ma wiele form. Regex dopasowujący „wywołanie funkcji" nie uwzględnił callable array — atakujący zawsze znajdzie równoważną konstrukcję poza wzorcem.
- Granice warstw. Restrykcja na poziomie PHP (
<) nie chroni, gdy ciąg powstaje na poziomie SQLite — filtruj tam, gdzie dane faktycznie się materializują.
Kluczowy takeaway: każda funkcja, która ostatecznie woła eval(), jest granicą zaufania — niezależnie od tego, jak „ograniczony" wydaje się sandbox. Filtrowanie regexami daje złudzenie bezpieczeństwa; realną barierą jest allowlist albo rezygnacja z eval.
Szukasz podatności w swojej aplikacji?
Jako niezależny badacz bezpieczeństwa i pentester pomagam organizacjom identyfikować i eliminować podatności, zanim zrobią to atakujący. Oferuję profesjonalne testy penetracyjne aplikacji webowych zgodne z OWASP ASVS.
Umów bezpłatną konsultację