CVE-2026-33944 SQL Injection in Dolibarr ERP/CRM — Third Party Update
SQL Injection in Dolibarr ERP/CRM v23.0.0 — the localtax1_value parameter reaches an SQL UPDATE query directly, without sanitization. Full database exfiltration, including administrator password hashes. CVSS 3.1: 8.8 (HIGH).
CVE-2026-33944 — SQL Injection in Dolibarr ERP/CRM via localtax1_value parameter
Responsible Disclosure: The vulnerability was reported to the Dolibarr team through a GitHub Security Advisory and acknowledged by the vendor. A fix was publicly released before the publication of this article. At the time of publication, GitHub Security Advisory GHSA-v5fq-cf5m-vwv7 remains unpublished despite the fix being available. Researcher: Grzegorz Tworek (Sec4check). If you run Dolibarr, make sure your instance has been updated.
Summary
During independent security research, I identified an SQL Injection vulnerability in the third-party (contractor) update function in Dolibarr ERP/CRM. The vulnerability affects the latest version v23.0.0 and likely all earlier versions as well. Reproduction was carried out on a clean, default Dolibarr installation (the official Docker image) with MariaDB 10.11.
The core of the problem: the lt1 parameter (local tax 1 value) is passed from user input directly into an SQL UPDATE query in the file societe/class/societe.class.php on line 1672 — with no escaping, type casting, or query parameterization whatsoever.
Notably, adjacent fields in the same SQL query (e.g. localtax1_assuj) are correctly cast to (int) or escaped via $db->escape(), which indicates that the developers were aware of the injection risk but did not apply the same protection to the tax-value fields.
| Type | CWE-89: SQL Injection |
|---|---|
| Product | Dolibarr ERP/CRM |
| Tested version | v23.0.0 (latest stable, March 2026) |
| Platform | Docker (official dolibarr/dolibarr image), MariaDB 10.11 |
| Component | societe/card.php + societe/class/societe.class.php |
| Attack vector | Network (authenticated POST request) |
| User interaction | None (the attacker exploits it directly) |
| CVSS 3.1 | 8.8 HIGH |
| CVSS vector | AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
Technical analysis
Root Cause
The vulnerability lives in the file societe/class/societe.class.php. The data flow looks as follows:
1. Input (societe/card.php, line 1135):
2. Sanitization (societe.class.php, line 1528) — insufficient:
3. SQL sink (societe.class.php, line 1672) — direct concatenation:
The alpha input filter strips HTML tags and double quotes, but it does not strip single quotes, parentheses, commas, spaces, or SQL keywords. Because the injection point sits in a numeric SQL context (the value is not wrapped in quotes), the attacker does not need any of the characters that the filter removes.
Key observation: The Dolibarr WAF (waf.inc.php) checks for SQL injection patterns only in GET parameters, not POST. The lt1 parameter sent via POST bypasses the WAF entirely. The same vulnerability exists for localtax2_value (the lt2 parameter) on line 1682.
Reproduction steps
- Install Dolibarr v23.0.0 (tested with the official Docker image, MariaDB 10.11)
- Log in as a user with the "Third parties: Create/Edit" permission (a standard user, not an admin)
- Create or open any third-party (contractor) record
- Click "Edit" to open the update form
- Intercept the POST request (e.g. with Burp Suite) and add the following parameters to the body:
localtax1assuj_value=1
lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1) - Submit the form (the server responds with HTTP 302 Found — the update succeeded)
- Go to the third party's "Notes" tab — the administrator's password MD5 hash is now visible in the "Note (private)" field
Verification: echo -n "admin123" | md5sum → 0192023a7bbd73250516f069df18b500 (identical to the extracted hash).
PoC (curl)
Evidence
Test environment: Dolibarr v23.0.0, official Docker image, MariaDB 10.11, macOS host.
Before exploitation — Notes tab (empty field)
Notes tab before exploitation — the "Note (private)" field is empty. Dolibarr v23.0.0, default configuration.
Burp Suite Repeater — POST request headers and the 302 response
Burp Suite Repeater — the full POST request to /societe/card.php with an HTTP/1.1 302 Found response confirming the update succeeded.
Burp Suite Repeater — the payload in the request body
The request body with the injected parameters: localtax1assuj_value=1 (line 181) and lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1) (line 187). The SQL subquery is passed without sanitization.
After exploitation — administrator password hash in the Note (private) field
Notes tab after exploitation — "Note (private)" contains 0192023a7bbd73250516f069df18b500, the MD5 hash of the administrator password ("admin123"), extracted from llx_user.pass_crypted via SQL injection.
Additional arithmetic evidence
Arithmetic evaluation: Sending lt1=3+4 causes localtax1_value=7.0000 to be stored in the database. The expression 3+4 was evaluated by the SQL engine rather than stored as a literal string — unambiguous proof of SQL injection.
Impact Assessment
An attacker holding the basic "Third parties: Create/Edit" permission (a standard user, not an administrator) can fully compromise the Dolibarr instance:
Full database read
Extraction of arbitrary data from the database: password hashes (pass_crypted), API keys (api_key), customer and supplier data, invoices, bank account details, internal notes.
Data modification
Injecting additional SET clauses into the UPDATE query allows modification of arbitrary records in the database — as demonstrated with the note_private field.
Privilege escalation
Extracting and cracking the administrator password hashes. Dolibarr uses unsalted MD5 by default, which makes cracking trivial (rainbow tables, hashcat).
Potential RCE
After escalating to admin — access to the Website module (PHP injection) or ModuleBuilder (writing PHP files). Potentially also via MariaDB functions (INTO OUTFILE, UDF).
CVSS 3.1 — detailed scoring
| Metric | Value |
|---|---|
| Attack Vector | Network — exploited over HTTP POST |
| Attack Complexity | Low — no special conditions |
| Privileges Required | Low — a standard user with the third-party edit permission |
| User Interaction | None — the attacker exploits the endpoint directly |
| Scope | Unchanged — impact limited to the Dolibarr application |
| Confidentiality | High — full database read (passwords, business data) |
| Integrity | High — ability to modify any record via injection |
| Availability | High — ability to DROP tables or corrupt critical data |
CVSS 3.1 Score: 8.8 (HIGH)
Vector: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Recommended remediation
Required fixes:
- Type casting on input (societe.class.php, line 1528):
$this->localtax1_value = (float) price2num(trim($this->localtax1_value)); - Type casting in the SQL query (societe.class.php, line 1672):
$sql .= ",localtax1_value =".((float) $this->localtax1_value); - Analogous fixes for
localtax2_value(lines 1529 and 1682)
Additional recommendations: extend the WAF to check for SQLi patterns in POST parameters, and change the password hashing algorithm from unsalted MD5 to bcrypt/argon2 to reduce the impact of future vulnerabilities that allow data exfiltration.
Disclosure Timeline
| Date | Event |
|---|---|
| 2026-03-20 | Discovery of the vulnerability during independent security research |
| 2026-03-20 | Reported via GitHub Security Advisory (GHSA-v5fq-cf5m-vwv7) |
| 2026-03-23 | Vendor acknowledged the report |
| 2026-03-25 | GitHub assigned CVE-2026-33944 |
| 2026-05-26 | Vendor released a fix in Dolibarr 22.0.5 (release notes reference GHSA-v5fq-cf5m-vwv7) |
| 2026-06-16 | Article published after the fix was released |
| 2026-06-16 | GitHub Security Advisory still remains unpublished |
Conclusions
This vulnerability is a textbook example of inconsistent input sanitization — one of the most common patterns leading to SQL injection. The developers applied correct (int) casting to the localtax1_assuj field in the same block of code, but overlooked the tax-value fields, likely on the assumption that the alpha filter provided sufficient protection.
This case illustrates several key principles:
- Defense in depth — a WAF that only checks GET is not enough; every layer must assume the previous one failed
- Query parameterization — string concatenation in SQL queries is never safe, regardless of input filters
- Consistency of defensive mechanisms — if you cast the type in one field, you must do it in every field of the same context
- Auditing the code "around" fixes — finding one safe field should prompt you to check every adjacent field
Key takeaway: Input filters (like alpha) are not a substitute for SQL query parameterization. In a numeric SQL context (no quotes), the attacker does not need any "special" characters — letters, parentheses, and commas, which most filters let through, are enough.
Looking for vulnerabilities in your application?
As an independent security researcher and penetration tester, I help organizations identify and eliminate vulnerabilities before attackers do. I offer professional web application penetration testing aligned with OWASP ASVS.
Book a free consultation