Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-47309: [CVE-2023-47309] Improper Neutralization of Input During Web Page Generation in Nukium - NKM GLS module for PrestaShop

Nukium nkmgls before version 3.0.2 is vulnerable to Cross Site Scripting (XSS) via NkmGlsCheckoutModuleFrontController::displayAjaxSavePhoneMobile.

CVE
#sql#xss#vulnerability#web#js#java#php#perl#auth

IMPORTANT NOTICE: DO NOT REPORT VULNERABILITIES SOLELY TO THE AUTHOR OR MARKETPLACE.

We urge you to report any vulnerabilities directly to us. Our mission is to ensure the safety and security of the PrestaShop ecosystem. Unfortunately, many module developers may not always recognize or acknowledge the vulnerabilities in their code, whether due to lack of awareness, or inability to properly evaluate the associated risk, or other reasons.

Given the rise in professional cybercrime networks actively seeking out these vulnerabilities, it’s crucial that any potential threats are promptly addressed and the community is informed. The most effective method to do this is by publishing a CVE, like the one provided below.

Should you discover any vulnerabilities, please report them to us at: report[@]security-presta.org or visit https://security-presta.org for more information.

Every vulnerability report helps make the community more secure, and we are profoundly grateful for any information shared with us.

In the module “NKM GLS” (nkmgls) up to version 3.0.1 from Nukium for PrestaShop, a guest (authenticated customer) can perform XSS injection of type 2 (stored XSS) from FRONT to BACK (F2B) of Category 2 within the funnel order in affected versions.

Note : To succeed in this exploit, the red team needs to pay to convert a cart into a valid order with GLS carrier and require the administrator of the PS to check a specific screen within its backoffice.

Summary

  • CVE ID: CVE-2023-47309
  • Published at: 2023-11-14
  • Platform: PrestaShop
  • Product: nkmgls
  • Impacted release: <= 3.0.1 (3.0.2 fixed the vulnerability)
  • Product author: Nukium
  • Weakness: CWE-79
  • Severity: critical (9.0)

Description

As all XSS type 2 (Stored XSS) F2B (Front to Back), there are two steps and a prerequisite.

Prerequisite :

  • The field phone_mobile within table gls_cart_carrier suffers from a type varchar(255) which is large enough to allow dangerous XSS payloads.

Steps :

  • The method NkmGlsCheckoutModuleFrontController::displayAjaxSavePhoneMobile does not properly clean the parameter gls_customer_mobile. pSQL is useless against XSS which exploits HTML tag attributes (Category 2 according to OWASP - pSQL only neutralized Category 1 thanks to its strip_tags).
  • The output in the backoffice is not escaped in the related smarty template that uses it.

CVSS base metrics

  • Attack vector: network
  • Attack complexity: low
  • Privilege required: low
  • User interaction: required
  • Scope: changed
  • Confidentiality: high
  • Integrity: high
  • Availability: high

Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

Possible malicious usage

  • Unlock design critical vulnerabilities, see this.

Patch from 3.0.2

--- a/controllers/front/checkout.php
+++ b/controllers/front/checkout.php

 <?php
 
+use Nukium\GLS\Common\Exception\GlsException;
 use Nukium\GLS\Common\Legacy\GlsController;
 
 require_once dirname(__FILE__) . '/../../vendor/autoload.php';
@@ -25,54 +26,63 @@ class NkmGlsCheckoutModuleFrontController extends ModuleFrontController
             'message' => ''
         );
 
-        $phone_mobile = Tools::getValue('gls_customer_mobile');
-        $id_carrier = Tools::getValue('id_carrier');
-        $is_relay = Tools::getValue('is_relay');
-
-        if ($cart && !empty($id_carrier)) {
-            /**
-             * Récupération du code produit GLS (dépend du transporteur sélectionné uniquement dans ce cas de figure
-             * car l'enregistrement est appelé uniquement pour les transporteurs gls13h ou glsrelais)
-             */
-            $customer_address = new Address($cart->id_address_delivery);
-            $customer_country_iso = '';
-            if ($customer_address) {
-                $customer_country_iso = Country::getIsoById($customer_address->id_country);
+        try {
+            $phone_mobile = Tools::getValue('gls_customer_mobile');
+            if (!Validate::isPhoneNumber($phone_mobile)) {
+                throw new GlsException($this->module->l('Please fill-in a valid mobile number (e.g. +XXXXXXXXXXX or 0XXXXXXXXX).', 'nkmgls'));
             }
-            $gls_product = $this->module->getGlsProductCode(
-                (int)$id_carrier,
-                $customer_country_iso
-            );
 
-            $query = new DbQuery();
-            $query->select('c.*')
-                ->from('gls_cart_carrier', 'c')
-                ->where('c.`id_customer` = ' . (int) $cart->id_customer)
-                ->where('c.`id_cart` = ' . (int) $cart->id);
-
-            if (Db::getInstance()->getRow($query)) {
-                $sql = 'UPDATE ' . _DB_PREFIX_ . 'gls_cart_carrier SET `customer_phone_mobile`=\'' . pSQL($phone_mobile) . '\', `id_carrier`=' . (int) $id_carrier . ', `gls_product`=\'' . pSQL($gls_product) . '\'';
-                // reset all data except mobile and gls_product
-                if (! $is_relay) {
-                    $sql .= ',`parcel_shop_id` = NULL, `name` = NULL, `address1` = NULL, `address2` = NULL, `postcode` = NULL,
-                        `city` = NULL, `phone` = NULL, `phone_mobile` = NULL, `id_country` = NULL, `parcel_shop_working_day` = NULL';
+            $id_carrier = Tools::getValue('id_carrier');
+            $is_relay = Tools::getValue('is_relay');
+
+            if ($cart && !empty($id_carrier)) {
+                /**
+                 * Récupération du code produit GLS (dépend du transporteur sélectionné uniquement dans ce cas de figure
+                 * car l'enregistrement est appelé uniquement pour les transporteurs gls13h ou glsrelais)
+                 */
+                $customer_address = new Address($cart->id_address_delivery);
+                $customer_country_iso = '';
+                if ($customer_address) {
+                    $customer_country_iso = Country::getIsoById($customer_address->id_country);
                 }
-                $sql .= ' WHERE `id_customer`=' . (int) $cart->id_customer . ' AND `id_cart`=' . (int) $cart->id;
+                $gls_product = $this->module->getGlsProductCode(
+                    (int)$id_carrier,
+                    $customer_country_iso
+                );
+
+                $query = new DbQuery();
+                $query->select('c.*')
+                    ->from('gls_cart_carrier', 'c')
+                    ->where('c.`id_customer` = ' . (int) $cart->id_customer)
+                    ->where('c.`id_cart` = ' . (int) $cart->id);
+
+                if (Db::getInstance()->getRow($query)) {
+                    $sql = 'UPDATE ' . _DB_PREFIX_ . 'gls_cart_carrier SET `customer_phone_mobile`=\'' . pSQL($phone_mobile) . '\', `id_carrier`=' . (int) $id_carrier . ', `gls_product`=\'' . pSQL($gls_product) . '\'';
+                    // reset all data except mobile and gls_product
+                    if (! $is_relay) {
+                        $sql .= ',`parcel_shop_id` = NULL, `name` = NULL, `address1` = NULL, `address2` = NULL, `postcode` = NULL,
+                            `city` = NULL, `phone` = NULL, `phone_mobile` = NULL, `id_country` = NULL, `parcel_shop_working_day` = NULL';
+                    }
+                    $sql .= ' WHERE `id_customer`=' . (int) $cart->id_customer . ' AND `id_cart`=' . (int) $cart->id;
 
-                if (Db::getInstance()->Execute($sql)) {
-                    $return['result'] = true;
-                } else {
-                    $return['message'] = $this->module->l('Unexpected error occurred.', self::L_SPECIFIC);
-                }
-            } else {
-                if (Db::getInstance()->Execute('INSERT INTO ' . _DB_PREFIX_ . 'gls_cart_carrier
-                    (`id_customer`, `id_cart`, `id_carrier`, `gls_product`, `customer_phone_mobile`)
-                    VALUES (' . (int) $cart->id_customer . ', ' . (int) $cart->id . ', ' . (int) $id_carrier . ', \'' . pSQL($gls_product) . '\', \'' . pSQL($phone_mobile) . '\')')) {
-                    $return['result'] = true;
+                    if (Db::getInstance()->Execute($sql)) {
+                        $return['result'] = true;
+                    } else {
+                        throw new GlsException($this->module->l('Unexpected error occurred.', self::L_SPECIFIC));
+                    }
                 } else {
-                    $return['message'] = $this->module->l('Unexpected error occurred.', self::L_SPECIFIC);
+                    if (Db::getInstance()->Execute('INSERT INTO ' . _DB_PREFIX_ . 'gls_cart_carrier
+                        (`id_customer`, `id_cart`, `id_carrier`, `gls_product`, `customer_phone_mobile`)
+                        VALUES (' . (int) $cart->id_customer . ', ' . (int) $cart->id . ', ' . (int) $id_carrier . ', \'' . pSQL($gls_product) . '\', \'' . pSQL($phone_mobile) . '\')')) {
+                        $return['result'] = true;
+                    } else {
+                        throw new GlsException($this->module->l('Unexpected error occurred.', self::L_SPECIFIC));
+                    }
                 }
             }
+        } catch (GlsException $e) {
+            $return['result'] = false;
+            $return['message'] = $e->getMessage();
         }
 
         header('Content-Type: application/json');


--- a/views/templates/admin/gls_label/label_list.tpl
+++ b/views/templates/admin/gls_label/label_list.tpl

@@ -186,7 +186,7 @@
                                                         {l s='Mobile' mod='nkmgls'}
                                                     </label>
                                                     <div>
-                                                        <input class="form-control" type="tel" name="mobile" value="{if !empty($tr.customer_phone_mobile)}{$tr.customer_phone_mobile}{else}{$tr.customer_phone}{/if}" required="required" />
+                                                        <input class="form-control" type="tel" name="mobile" value="{if !empty($tr.customer_phone_mobile)}{$tr.customer_phone_mobile|escape:'html':'UTF-8'}{else}{$tr.customer_phone|escape:'html':'UTF-8'}{/if}" required="required" />
                                                     </div>
                                                 </div>
                                                 {if $tr.id_country|in_array:$cee_countries === false}
@@ -210,10 +210,10 @@

Other recommendations

  • It’s recommended to upgrade to the latest version of the module nkmgls.

  • Systematically escape characters ‘ “ < and > by replacing them with HTML entities and applying strip_tags - Smarty and Twig provide auto-escape filters : ```diff

  • Smarty: {$value.comment

    escape:’html’:’UTF-8’}

  • Twig: {{value.comment|e}}(without backslashes) ```

  • Limit to the strict minimum the length’s value in database - a database field that allow 10 characters (varchar(10)) is far less dangerous than a field that allows 40+ characters (use cases that can exploit fragmented XSS payloads are very rare)

  • Configure CSP headers (content security policies) by listing external domains allowed to load assets (such as js files) or being called in XHR transactions (Ajax).

  • If applicable: check against all your frontoffice’s uploaders, uploading files that will be served by your server that mime type application/javascript (like every .js natively) must be strictly forbidden as it must be considered as dangerous as PHP files.

  • Activate OWASP 941’s rules on your WAF (Web application firewall) - be warned that you will probably break your frontoffice/backoffice and you will need to preconfigure some bypasses against this set of rules.

Timeline

Date

Action

2023-02-24

Issue discovered during a code review by TouchWeb.fr

2023-02-24

Contact the author who will provide a patch within 4 hours

2023-02-24

V3.0.2 available on https://store.nukium.com/ and https://addons.prestashop.com/

2023-03-10

Recontact author about the publication of the vulnerability

2023-03-12

Author completes the diff of this present CVE and asks for a delay to publish

2023-05-05

Recontact author about the publication of the vulnerability

2023-05-17

Author ask for another delay before publication

2023-07-15

Recontact author about the publication of the vulnerability

2023-09-17

Recontact author about the publication of the vulnerability

2023-09-30

Recontact author about the publication of the vulnerability

2023-10-30

Inform the author about the publication of the vulnerability

2023-10-30

Request a CVE ID

2023-11-08

Received CVE ID

2023-11-14

Publish this security advisory

Links

  • Author product page
  • PrestaShop addons product page
  • National Vulnerability Database

DISCLAIMER: The French Association Friends Of Presta (FOP) acts as an intermediary to help hosting this advisory. While we strive to ensure the information and advice provided are accurate, FOP cannot be held liable for any consequences arising from reported vulnerabilities or any subsequent actions taken.

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907