CVE-2022-1457: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Store XSS in title parameter executing at EditUser Page & EditProducto page in GitHub repository neorazorx/facturascripts prior to 2022.04. Cross-site scripting attacks can have devastating consequences. Code injected into a vulnerable application can exfiltrate data or install malware on the user's machine. Attackers can masquerade as authorized users via session cookies, allowing them to perform any action allowed by the user account.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2022-04-25T10:15Z
Repository: https://github.com/neorazorx/facturascripts
Language: PHP
Files changed: Core/Controller/EditPageOption.php, Core/Translation/gl_ES.json
Commit date: 2022-04-21 16:18:14 +0200
Commit message: Solucionado bug XSS al colocar javascript como título en un page_option.

Diff: diff --git a/Core/Controller/EditPageOption.php b/Core/Controller/EditPageOption.php
index 702e0a82f..a9b620f8d 100644
--- a/Core/Controller/EditPageOption.php
+++ b/Core/Controller/EditPageOption.php
@@ -1,9 +1,9 @@
 <?php
 /**
  * This file is part of FacturaScripts
- * Copyright (C) 2017-2020 Carlos Garcia Gomez <carlos@facturascripts.com>
+ * Copyright (C) 2017-2022 Carlos Garcia Gomez <carlos@facturascripts.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
@@ -14,10 +14,11 @@
  * GNU Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
+
 namespace FacturaScripts\Core\Controller;
 
 use FacturaScripts\Core\Base\Controller;
 use FacturaScripts\Core\Base\ControllerPermissions;
 use FacturaScripts\Core\Base\DataBase\DataBaseWhere;
@@ -43,17 +44,15 @@ class EditPageOption extends Controller
      * @var string
      */
     public $backPage;
 
     /**
-     *
      * @var array
      */
     public $columns = [];
 
     /**
-     *
      * @var array
      */
     public $modals = [];
 
     /**
@@ -62,11 +61,10 @@ class EditPageOption extends Controller
      * @var PageOption
      */
     public $model;
 
     /**
-     *
      * @var array
      */
     public $rows = [];
 
     /**
@@ -81,16 +79,11 @@ class EditPageOption extends Controller
      *
      * @var string
      */
     public $selectedViewName;
 
-    /**
-     * Returns basic page attributes
-     *
-     * @return array
-     */
-    public function getPageData()
+    public function getPageData(): array
     {
         $data = parent::getPageData();
         $data['menu'] = 'admin';
         $data['showonmenu'] = false;
         $data['title'] = 'options';
@@ -101,11 +94,11 @@ class EditPageOption extends Controller
     /**
      * Get the list of users, excluding the user admin
      *
      * @return array
      */
-    public function getUserList()
+    public function getUserList(): array
     {
         $result = [];
         $users = CodeModel::all(User::tableName(), 'nick', 'nick', false);
         foreach ($users as $codeModel) {
             if ($codeModel->code != 'admin') {
@@ -117,12 +110,12 @@ class EditPageOption extends Controller
     }
 
     /**
      * Runs the controller's private logic.
      *
-     * @param Response              $response
-     * @param User                  $user
+     * @param Response $response
+     * @param User $user
      * @param ControllerPermissions $permissions
      */
     public function privateCore(&$response, $user, $permissions)
     {
         parent::privateCore($response, $user, $permissions);
@@ -182,16 +175,16 @@ class EditPageOption extends Controller
     }
 
     protected function loadSelectedViewName()
     {
         $code = $this->request->get('code', '');
-        if (false === \strpos($code, '-')) {
+        if (false === strpos($code, '-')) {
             $this->selectedViewName = $code;
             return;
         }
 
-        $parts = \explode('-', $code);
+        $parts = explode('-', $code);
         $this->selectedViewName = empty($parts) ? $code : $parts[0];
     }
 
     /**
      * Save new configuration for view
@@ -264,15 +257,16 @@ class EditPageOption extends Controller
         $where = [
             new DataBaseWhere('name', $this->selectedViewName),
             new DataBaseWhere('nick', $this->selectedUser),
         ];
         if ($this->model->loadFromCode('', $where)) {
-            return true;  // Existen opciones para el usuario.
+            // Existen opciones para el usuario.
+            return true;
         }
 
-        if (false == $this->loadPageOptionsForAll()) {
-            // No existe opciones general. Asignamos las opciones por defecto de la vista xml al usuario.
+        if (false === $this->loadPageOptionsForAll()) {
+            // No existe opciones generales. Asignamos las opciones por defecto de la vista xml al usuario.
             $this->model->nick = $this->selectedUser;
             return false;
         }
 
         // No existe opciones para el usuario. Clonamos las generales.
@@ -280,20 +274,19 @@ class EditPageOption extends Controller
         $this->model->nick = $this->selectedUser;
         return true;
     }
 
     /**
-     *
-     * @param array  $column
+     * @param array $column
      * @param string $name
      * @param string $key
-     * @param bool   $isWidget
-     * @param bool   $allowEmpty
+     * @param bool $isWidget
+     * @param bool $allowEmpty
      */
     private function setColumnOption(&$column, string $name, string $key, bool $isWidget, bool $allowEmpty)
     {
-        $newValue = $this->request->request->get($name . '-' . $key);
+        $newValue = self::toolBox()::utils()::noHtml($this->request->request->get($name . '-' . $key));
         if ($isWidget) {
             if (!empty($newValue) || $allowEmpty) {
                 $column['children'][0][$key] = $newValue;
             }
             return;
diff --git a/Core/Translation/gl_ES.json b/Core/Translation/gl_ES.json
index b48e18215..2efdb5ca2 100644
--- a/Core/Translation/gl_ES.json
+++ b/Core/Translation/gl_ES.json
@@ -354,11 +354,11 @@
     "desc-parent-family": "Indica se a familia está agrupada noutro nivel superior.",
     "desc-payment-days": "Exemplo: 1,15,31",
     "desc-provicer-business-name-2": "Descrición nome de empresa de provedor 2",
     "desc-provider-business-name": "Descrición nome de empresa de provedor",
     "desc-provider-name": "Descrición nome de provedor",
-    "desc-rate-code": "Código para uso interno da tarifa.",
+    "desc-rate-code": "Código para uso interno da taxa.",
     "desc-vat-document": "Identificador do documento onde se aplica o imposto",
     "description": "Descrición",
     "description-1": "Descrición 1",
     "description-2": "Descrición 2",
     "description-3": "Descrición 3",
@@ -969,12 +969,12 @@
     "quantity": "Cantidade",
     "quantity-abb": "Cant.",
     "quantity-served": "cantidade servida",
     "quantity-sold": "Cant. vendida",
     "randomizer-generating-more-items": "Xerando máis elementos...",
-    "rate": "Tarifa",
-    "rates": "Tarifas",
+    "rate": "Taxa",
+    "rates": "Taxas",
     "re": "RE",
     "re-open": "Reabrir",
     "read-more": "Ler máis",
     "readonly": "Só lectura",
     "rebuild": "Reconstruir",

Vulnerable code:  * Copyright (C) 2017-2020 Carlos Garcia Gomez <carlos@facturascripts.com>
     *
     *
     *
    /**
     * Returns basic page attributes
     *
     * @return array
     */
    public function getPageData()
    public function getUserList()
     * @param Response              $response
     * @param User                  $user
        if (false === \strpos($code, '-')) {
        $parts = \explode('-', $code);
            return true;  // Existen opciones para el usuario.
        if (false == $this->loadPageOptionsForAll()) {
            // No existe opciones general. Asignamos las opciones por defecto de la vista xml al usuario.
     *
     * @param array  $column
     * @param bool   $isWidget
     * @param bool   $allowEmpty
        $newValue = $this->request->request->get($name . '-' . $key);
    "desc-rate-code": "Código para uso interno da tarifa.",
    "rate": "Tarifa",
    "rates": "Tarifas",
Fixed code:  * Copyright (C) 2017-2022 Carlos Garcia Gomez <carlos@facturascripts.com>

    public function getPageData(): array
    public function getUserList(): array
     * @param Response $response
     * @param User $user
        if (false === strpos($code, '-')) {
        $parts = explode('-', $code);
            // Existen opciones para el usuario.
            return true;
        if (false === $this->loadPageOptionsForAll()) {
            // No existe opciones generales. Asignamos las opciones por defecto de la vista xml al usuario.
     * @param array $column
     * @param bool $isWidget
     * @param bool $allowEmpty
        $newValue = self::toolBox()::utils()::noHtml($this->request->request->get($name . '-' . $key));
    "desc-rate-code": "Código para uso interno da taxa.",
    "rate": "Taxa",
    "rates": "Taxas",