CVE-2021-43091: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: An SQL Injection vlnerability exits in Yeswiki doryphore 20211012 via the email parameter in the registration form.
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: 7.5
Published: 2022-03-25T17:15Z
Repository: https://github.com/yeswiki/yeswiki
Language: PHP
Files changed: actions/listpages.php, actions/newtextsearch.php, includes/User.class.php, tools/bazar/services/FormManager.php, tools/security/actions/despam.php
Commit date: 2021-10-20 01:36:49 +0200
Commit message: fix(SQL query): use in all case escape to format query

Diff: diff --git a/actions/listpages.php b/actions/listpages.php
index 0daa4abf..42f8b8ad 100755
--- a/actions/listpages.php
+++ b/actions/listpages.php
@@ -113,11 +113,11 @@ if ($tree) {
             break;
         case 'tag':
             $links[$tree] = array();
     } // switch
     if ($sort != 'tag') {
-        $sql .= ' WHERE a.tag = "' . AddSlashes($tree) . '" AND a.latest = "Y" LIMIT 1';
+        $sql .= ' WHERE a.tag = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($tree) . '" AND a.latest = "Y" LIMIT 1';
         if (!$rootData = $this->LoadSingle($sql)) {
             echo '<div class="alert alert-danger"><strong>'._t('ERROR').' '._t('ACTION').' ListPages</strong> : '._('THE_PAGE').' ' . htmlspecialchars($tree, ENT_COMPAT, YW_CHARSET) . ' '._t('DOESNT_EXIST').' !</div>';
             return;
         }
         $links[$tree] = $rootData;
@@ -130,12 +130,12 @@ if ($tree) {
     // $workingon represents every page of the current level
     $workingon = array($tree => &$links[$tree]['haslinksto']);
     
     // to avoid many loops and computing several time the lists needed for the request,
     // we store them into variables
-    $from = '"' . AddSlashes($tree) . '"';
-    $exclude[] = AddSlashes($tree);
+    $from = '"' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($tree) . '"';
+    $exclude[] = $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($tree);
     $exclude_str = '"' . implode('", "', $exclude) . '"';
     for ($i = 1; $i <= $levels; $i++) {
         if ($from) {
             if ($owner) {
                 $sql = 'SELECT from_tag, to_tag, a.tag IS NOT NULL page_exists'
@@ -149,11 +149,11 @@ if ($tree) {
                     $sql .= ' FROM ' . $prefix . 'links, ' . $prefix . 'pages a';
                 }
                 $sql .= ' WHERE from_tag IN (' . $from . ')'
                     . ' AND to_tag NOT IN (' . $from . ')'
                     . ' AND to_tag = a.tag'
-                    . ' AND a.owner = "' . AddSlashes($owner) . '"'
+                    . ' AND a.owner = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($owner) . '"'
                     . ' AND a.latest = "Y"';
             } else {
                 $sql = 'SELECT from_tag, to_tag, a.tag IS NOT NULL page_exists';
                 switch ($sort) {
                     case 'owner':
@@ -206,11 +206,11 @@ if ($tree) {
         
         if ($pages = $this->LoadAll($sql)) {
             $from = '';
             $newworkingon = array();
             foreach ($pages as $page) {
-                $to_tag = '"' . AddSlashes($page['to_tag']) . '"';
+                $to_tag = '"' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($page['to_tag']) . '"';
                 $workingon[$page['from_tag']][$page['to_tag']] = array('page_exists' => $page['page_exists'], 'haslinksto' => array());
                 if ($sort != 'tag') {
                     $workingon[$page['from_tag']][$page['to_tag']][$sort] = $page[$sort];
                     switch ($sort) {
                         case 'owner':
@@ -314,24 +314,24 @@ if ($tree) {
             . ($owner ? '' : ', b.owner, owner_page.tag IS NOT NULL owner_has_ownpage')
             . ' FROM ' . $prefix . 'pages a, ' . $prefix . 'pages b
             LEFT JOIN ' . $prefix . 'users ON b.user = name
             LEFT JOIN ' . $prefix . 'pages user_page ON name = user_page.tag AND user_page.latest = "Y"'
             . ($owner ? '' : ' LEFT JOIN ' . $prefix . 'pages owner_page ON b.owner = owner_page.tag AND owner_page.latest = "Y"')
-            . ' WHERE a.user = "' . AddSlashes($user) . '"'
+            . ' WHERE a.user = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($user) . '"'
             . ' AND a.tag = b.tag AND b.latest = "Y"'
-            . ($owner ? ' AND b.owner = "' . AddSlashes($owner) . '"' : '');
+            . ($owner ? ' AND b.owner = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($owner) . '"' : '');
     } elseif ($owner) {
         if ($sort == 'user') {
             $sql = 'SELECT a.tag, a.time,
                 a.user, name IS NOT NULL user_is_registered, user_page.tag IS NOT NULL user_has_ownpage
                 FROM ' . $prefix . 'pages a
                 LEFT JOIN ' . $prefix . 'users ON a.user = name
                 LEFT JOIN ' . $prefix . 'pages user_page ON name = user_page.tag AND user_page.latest = "Y"';
         } else {
             $sql = 'SELECT tag, time FROM ' . $prefix . 'pages a';
         }
-        $sql .= ' WHERE a.owner = "' . AddSlashes($owner) . '" AND a.latest = "Y"';
+        $sql .= ' WHERE a.owner = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($owner) . '" AND a.latest = "Y"';
     } else {
         if ($sort == 'user') {
             $sql = 'SELECT a.tag, a.owner,
                 owner_page.tag IS NOT NULL owner_has_ownpage,
                 a.user, name IS NOT NULL user_is_registered, user_page.tag IS NOT NULL user_has_ownpage
diff --git a/actions/newtextsearch.php b/actions/newtextsearch.php
index 1ebd2908..82199e91 100644
--- a/actions/newtextsearch.php
+++ b/actions/newtextsearch.php
@@ -146,11 +146,11 @@ if ($phrase) {
         $requeteSQLForList = ' OR ('.$requeteSQLForList.') ';
     }
     
     // Modification de caractère spéciaux
     $phraseFormatted= str_replace(array('*', '?'), array('%', '_'), $phrase);
-    $phraseFormatted = addslashes($phraseFormatted);
+    $phraseFormatted = $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($phraseFormatted);
 
     // Blablabla SQL
     $requestfull = 'SELECT body, tag FROM '.$prefixe.'pages
                   LEFT JOIN '.$prefixe.'acls ON tag = page_tag AND privilege = "read"
                   WHERE latest = "Y"
diff --git a/includes/User.class.php b/includes/User.class.php
index f81597e4..97374fb3 100644
--- a/includes/User.class.php
+++ b/includes/User.class.php
@@ -998,11 +998,11 @@ class User
     */
     protected function emailExistsInDB($email)
     {
         /* Build sql query*/
         $sql  = 'SELECT * FROM '.$this->usersTable;
-        $sql .= ' WHERE email = "'.$email.'";';
+        $sql .= ' WHERE email = "'.mysqli_real_escape_string($this->wiki->dblink,$email).'";';
         /* Execute query */
         $results = $this->wiki->loadAll($sql);
         return $results; // If the password does not already exist in DB, $result is an empty table => false
     }
 
diff --git a/tools/bazar/services/FormManager.php b/tools/bazar/services/FormManager.php
index 13710060..2332c9f5 100644
--- a/tools/bazar/services/FormManager.php
+++ b/tools/bazar/services/FormManager.php
@@ -44,11 +44,11 @@ class FormManager
     {
         if (isset($this->cachedForms[$formId])) {
             return $this->cachedForms[$formId];
         }
 
-        $form = $this->dbService->loadSingle('SELECT * FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=\'' . $formId . '\'');
+        $form = $this->dbService->loadSingle('SELECT * FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=\'' . $this->dbService->escape($formId) . '\'');
 
         if (!$form) {
             return null;
         }
 
@@ -103,33 +103,33 @@ class FormManager
         }
 
         return $this->dbService->query('INSERT INTO ' . $this->dbService->prefixTable('nature')
             . '(`bn_id_nature` ,`bn_ce_i18n` ,`bn_label_nature` ,`bn_template` ,`bn_description` ,`bn_sem_context` ,`bn_sem_type` ,`bn_sem_use_template` ,`bn_condition`)'
             . ' VALUES (' . $data['bn_id_nature'] . ', "fr-FR", "'
-            . addslashes(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '","'
-            . addslashes(_convert($data['bn_template'], YW_CHARSET, true)) . '", "'
-            . addslashes(_convert($data['bn_description'], YW_CHARSET, true)) . '", "'
-            . addslashes(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '", "'
-            . addslashes(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '", '
+            . $this->dbService->escape(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '","'
+            . $this->dbService->escape(_convert($data['bn_template'], YW_CHARSET, true)) . '", "'
+            . $this->dbService->escape(_convert($data['bn_description'], YW_CHARSET, true)) . '", "'
+            . $this->dbService->escape(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '", "'
+            . $this->dbService->escape(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '", '
             . (isset($data['bn_sem_use_template']) ? '1' : '0') . ', "'
-            . addslashes(_convert($data['bn_condition'], YW_CHARSET, true)) . '")');
+            . $this->dbService->escape(_convert($data['bn_condition'], YW_CHARSET, true)) . '")');
     }
 
     public function update($data)
     {
         if ($this->securityController->isWikiHibernated()) {
             throw new \Exception(_t('WIKI_IN_HIBERNATION'));
         }
         return $this->dbService->query('UPDATE' . $this->dbService->prefixTable('nature') . 'SET '
-            . '`bn_label_nature`="' . addslashes(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '" ,'
-            . '`bn_template`="' . addslashes(_convert($data['bn_template'], YW_CHARSET, true)) . '" ,'
-            . '`bn_description`="' . addslashes(_convert($data['bn_description'], YW_CHARSET, true)) . '" ,'
-            . '`bn_sem_context`="' . addslashes(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '" ,'
-            . '`bn_sem_type`="' . addslashes(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '" ,'
+            . '`bn_label_nature`="' . $this->dbService->escape(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '" ,'
+            . '`bn_template`="' . $this->dbService->escape(_convert($data['bn_template'], YW_CHARSET, true)) . '" ,'
+            . '`bn_description`="' . $this->dbService->escape(_convert($data['bn_description'], YW_CHARSET, true)) . '" ,'
+            . '`bn_sem_context`="' . $this->dbService->escape(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '" ,'
+            . '`bn_sem_type`="' . $this->dbService->escape(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '" ,'
             . '`bn_sem_use_template`=' . (isset($data['bn_sem_use_template']) ? '1' : '0') . ' ,'
-            . '`bn_condition`="' . addslashes(_convert($data['bn_condition'], YW_CHARSET, true)) . '"'
-            . ' WHERE `bn_id_nature`=' . $data['bn_id_nature']);
+            . '`bn_condition`="' . $this->dbService->escape(_convert($data['bn_condition'], YW_CHARSET, true)) . '"'
+            . ' WHERE `bn_id_nature`=' . $this->dbService->escape($data['bn_id_nature']));
     }
 
     public function clone($id)
     {
         $data = $this->getOne($id);
@@ -153,11 +153,11 @@ class FormManager
         if (strval(intval($id)) != strval($id)) {
             return null ;
         }
 
         $this->clear($id);
-        return $this->dbService->query('DELETE FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=' . $id);
+        return $this->dbService->query('DELETE FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=' . $this->dbService->escape($id));
     }
 
     public function clear($id)
     {
         if ($this->securityController->isWikiHibernated()) {
@@ -165,18 +165,18 @@ class FormManager
         }
         $this->dbService->query(
             'DELETE FROM' . $this->dbService->prefixTable('acls') .
             'WHERE page_tag IN (SELECT tag FROM ' . $this->dbService->prefixTable('pages') .
             'WHERE tag IN (SELECT resource FROM ' . $this->dbService->prefixTable('triples') .
-            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $id . '"%\' );'
+            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $this->dbService->escape($id) . '"%\' );'
         );
 
         // TODO use PageManager
         $this->dbService->query(
             'DELETE FROM' . $this->dbService->prefixTable('pages') .
             'WHERE tag IN (SELECT resource FROM ' . $this->dbService->prefixTable('triples') .
-            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $id . '"%\';'
+            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $this->dbService->escape($id) . '"%\';'
         );
 
         // TODO use TripleStore
         $this->dbService->query(
             'DELETE FROM' . $this->dbService->prefixTable('triples') .
diff --git a/tools/security/actions/despam.php b/tools/security/actions/despam.php
index 62194fa4..60260851 100755
--- a/tools/security/actions/despam.php
+++ b/tools/security/actions/despam.php
@@ -60,11 +60,11 @@ if ($this->UserIsAdmin()) {
         if (isset($_POST['from']) && isset($_POST['2'])) {
             $requete =
               "select *
               from ".$this->config["table_prefix"]."pages
               where
-              time > date_sub(now(), interval " . addslashes($_POST['from']) . " hour)
+              time > date_sub(now(), interval " . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($_POST['from']) . " hour)
               and latest = 'Y'
               order by `time` desc";
             $title =
               "<h2>Nettoyage des pages vandalisées depuis " .
               $_POST['from'] . " heure(s)</h2>\n";

Vulnerable code:         $sql .= ' WHERE a.tag = "' . AddSlashes($tree) . '" AND a.latest = "Y" LIMIT 1';
    $from = '"' . AddSlashes($tree) . '"';
    $exclude[] = AddSlashes($tree);
                    . ' AND a.owner = "' . AddSlashes($owner) . '"'
                $to_tag = '"' . AddSlashes($page['to_tag']) . '"';
            . ' WHERE a.user = "' . AddSlashes($user) . '"'
            . ($owner ? ' AND b.owner = "' . AddSlashes($owner) . '"' : '');
        $sql .= ' WHERE a.owner = "' . AddSlashes($owner) . '" AND a.latest = "Y"';
    $phraseFormatted = addslashes($phraseFormatted);
        $sql .= ' WHERE email = "'.$email.'";';
        $form = $this->dbService->loadSingle('SELECT * FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=\'' . $formId . '\'');
            . addslashes(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '","'
            . addslashes(_convert($data['bn_template'], YW_CHARSET, true)) . '", "'
            . addslashes(_convert($data['bn_description'], YW_CHARSET, true)) . '", "'
            . addslashes(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '", "'
            . addslashes(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '", '
            . addslashes(_convert($data['bn_condition'], YW_CHARSET, true)) . '")');
            . '`bn_label_nature`="' . addslashes(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '" ,'
            . '`bn_template`="' . addslashes(_convert($data['bn_template'], YW_CHARSET, true)) . '" ,'
            . '`bn_description`="' . addslashes(_convert($data['bn_description'], YW_CHARSET, true)) . '" ,'
            . '`bn_sem_context`="' . addslashes(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '" ,'
            . '`bn_sem_type`="' . addslashes(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '" ,'
            . '`bn_condition`="' . addslashes(_convert($data['bn_condition'], YW_CHARSET, true)) . '"'
            . ' WHERE `bn_id_nature`=' . $data['bn_id_nature']);
        return $this->dbService->query('DELETE FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=' . $id);
            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $id . '"%\' );'
            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $id . '"%\';'
              time > date_sub(now(), interval " . addslashes($_POST['from']) . " hour)
Fixed code:         $sql .= ' WHERE a.tag = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($tree) . '" AND a.latest = "Y" LIMIT 1';
    $from = '"' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($tree) . '"';
    $exclude[] = $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($tree);
                    . ' AND a.owner = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($owner) . '"'
                $to_tag = '"' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($page['to_tag']) . '"';
            . ' WHERE a.user = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($user) . '"'
            . ($owner ? ' AND b.owner = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($owner) . '"' : '');
        $sql .= ' WHERE a.owner = "' . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($owner) . '" AND a.latest = "Y"';
    $phraseFormatted = $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($phraseFormatted);
        $sql .= ' WHERE email = "'.mysqli_real_escape_string($this->wiki->dblink,$email).'";';
        $form = $this->dbService->loadSingle('SELECT * FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=\'' . $this->dbService->escape($formId) . '\'');
            . $this->dbService->escape(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '","'
            . $this->dbService->escape(_convert($data['bn_template'], YW_CHARSET, true)) . '", "'
            . $this->dbService->escape(_convert($data['bn_description'], YW_CHARSET, true)) . '", "'
            . $this->dbService->escape(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '", "'
            . $this->dbService->escape(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '", '
            . $this->dbService->escape(_convert($data['bn_condition'], YW_CHARSET, true)) . '")');
            . '`bn_label_nature`="' . $this->dbService->escape(_convert($data['bn_label_nature'], YW_CHARSET, true)) . '" ,'
            . '`bn_template`="' . $this->dbService->escape(_convert($data['bn_template'], YW_CHARSET, true)) . '" ,'
            . '`bn_description`="' . $this->dbService->escape(_convert($data['bn_description'], YW_CHARSET, true)) . '" ,'
            . '`bn_sem_context`="' . $this->dbService->escape(_convert($data['bn_sem_context'], YW_CHARSET, true)) . '" ,'
            . '`bn_sem_type`="' . $this->dbService->escape(_convert($data['bn_sem_type'], YW_CHARSET, true)) . '" ,'
            . '`bn_condition`="' . $this->dbService->escape(_convert($data['bn_condition'], YW_CHARSET, true)) . '"'
            . ' WHERE `bn_id_nature`=' . $this->dbService->escape($data['bn_id_nature']));
        return $this->dbService->query('DELETE FROM ' . $this->dbService->prefixTable('nature') . 'WHERE bn_id_nature=' . $this->dbService->escape($id));
            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $this->dbService->escape($id) . '"%\' );'
            'WHERE property="http://outils-reseaux.org/_vocabulary/type" AND value="fiche_bazar") AND body LIKE \'%"id_typeannonce":"' . $this->dbService->escape($id) . '"%\';'
              time > date_sub(now(), interval " . $this->services->get(\YesWiki\Core\Service\DbService::class)->escape($_POST['from']) . " hour)