CVE-2022-4933: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: A vulnerability, which was classified as critical, has been found in ATM Consulting dolibarr_module_quicksupplierprice up to 1.1.6. Affected by this issue is the function upatePrice of the file script/interface.php. The manipulation leads to sql injection. The attack may be launched remotely. Upgrading to version 1.1.7 is able to address this issue. The patch is identified as ccad1e4282b0e393a32fcc852e82ec0e0af5446f. It is recommended to upgrade the affected component. VDB-223382 is the identifier assigned to this vulnerability.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2023-03-20T05:15Z
Repository: https://github.com/ATM-Consulting/dolibarr_module_quicksupplierprice
Language: PHP
Files changed: ChangeLog.md, core/modules/modquicksupplierprice.class.php, script/interface.php
Commit date: 2022-06-29 16:47:27 +0200
Commit message: FIX : `Interface.php` has fatal errors (invisible to user) due to SQL injection of empty input values - *29/06/2022* - 1.1.7

Diff: diff --git a/ChangeLog.md b/ChangeLog.md
index 8676d17..e38890e 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -3,10 +3,12 @@ All notable changes to this project will be documented in this file.
 
 ## Version 1.0
 
 ### Changed
 
+- FIX : `Interface.php` has fatal errors (invisible to user) due to SQL
+  injection of empty input values - *29/06/2022* - 1.1.7
 - FIX : Can't create more product prices if multidevise is enable - *01/06/2022* - 1.1.6
 - FIX : UX Changes between DOL 13.0 and 14.0 so we pull the qsp form under addline tpl - *02/05/2022* - 1.1.5
 - FIX : tvatx must not be converted to int, because it can have decimals and specific tva code - *30/03/2022* - 1.1.4
 - FIX : Fill the unit price to be used by the addline action of fourn/commande/card.php which has changed between V12 and V13 - *22/12/2021* - 1.1.3
 - FIX : Compatibility V13 - Add token renewal - *18/05/2021* - 1.1.2
diff --git a/core/modules/modquicksupplierprice.class.php b/core/modules/modquicksupplierprice.class.php
index a5169eb..2d7807c 100644
--- a/core/modules/modquicksupplierprice.class.php
+++ b/core/modules/modquicksupplierprice.class.php
@@ -56,11 +56,11 @@ class modquicksupplierprice extends DolibarrModules
 		// Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module)
 		$this->name = preg_replace('/^mod/i','',get_class($this));
 		// Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module)
 		$this->description = "Description of module quicksupplierprice";
 		// Possible values for version are: 'development', 'experimental', 'dolibarr' or version
-		$this->version = '1.1.6';
+		$this->version = '1.1.7';
 		// Key used in llx_const table to save module status enabled/disabled (where MYMODULE is value of property name of module in uppercase)
 		$this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
 		// Where to store the module in setup page (0=common,1=interface,2=others,3=very specific)
 		$this->special = 0;
 		// Name of image file used for this module.
diff --git a/script/interface.php b/script/interface.php
index 3d80980..848e3f9 100644
--- a/script/interface.php
+++ b/script/interface.php
@@ -116,30 +116,40 @@
      * @param unknown $tvatx
      */
     function upatePrice($id_prod, $fk_soc, $unitprice, $qte, $ref_search, $price, $ref, $tvatx){
         global $db, $user;
 
+		if ($price === '' || $unitprice === '') {
+			print json_encode(array('retour' => 0, 'error' => 'prix non renseigné'));
+			return;
+		}
+
         ob_start();
 
         // Clean vat code
         $vat_src_code = '';
         if (preg_match('/\((.*)\)/', $tvatx, $reg)) {
         	$vat_src_code = $reg[1];
         	$tvatx = preg_replace('/\s*\(.*\)/', '', $tvatx); // Remove code into vatrate.
         }
 
         // On vérifie si la ligne de tarif n'existe pas déjà pour ce fournisseur
-        $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price WHERE fk_product=" . $id_prod;
-        $sql .= " AND fk_soc=" . $fk_soc;
-        $sql .= " AND unitprice=" . $unitprice;
-        $sql .= " AND quantity=" . $qte;
+		$sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'product_fournisseur_price'
+			. ' WHERE fk_product=' . intval($id_prod)
+			. ' AND fk_soc=' . intval($fk_soc)
+			. ' AND unitprice=' . floatval($unitprice)
+			. ' AND quantity=' . intval($qte);
         if (!empty($vat_src_code)) {
-        	$sql .= " AND default_vat_code='" . $vat_src_code."'";
+        	$sql .= ' AND default_vat_code="' . $db->escape($vat_src_code).'"';
         }
 
 
         $resq = $db->query($sql);
+		if (!$resq) {
+			print json_encode(array('retour' => 0, 'error' => $db->lasterror()));
+			return;
+		}
 
         if($resq->num_rows !== 0){ // s'il existe, on renvoie l'id de cet ligne prix
             $obj = $db->fetch_object($resq);
             $ret = $obj->rowid;
         } else { // si on ne trouve rien, création du prix fournisseur

Vulnerable code: 		$this->version = '1.1.6';
        $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_fournisseur_price WHERE fk_product=" . $id_prod;
        $sql .= " AND fk_soc=" . $fk_soc;
        $sql .= " AND unitprice=" . $unitprice;
        $sql .= " AND quantity=" . $qte;
        	$sql .= " AND default_vat_code='" . $vat_src_code."'";
Fixed code: - FIX : `Interface.php` has fatal errors (invisible to user) due to SQL
  injection of empty input values - *29/06/2022* - 1.1.7
		$this->version = '1.1.7';
		if ($price === '' || $unitprice === '') {
			print json_encode(array('retour' => 0, 'error' => 'prix non renseigné'));
			return;
		}

		$sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'product_fournisseur_price'
			. ' WHERE fk_product=' . intval($id_prod)
			. ' AND fk_soc=' . intval($fk_soc)
			. ' AND unitprice=' . floatval($unitprice)
			. ' AND quantity=' . intval($qte);
        	$sql .= ' AND default_vat_code="' . $db->escape($vat_src_code).'"';
		if (!$resq) {
			print json_encode(array('retour' => 0, 'error' => $db->lasterror()));
			return;
		}