Vulnerability: Tuleap Open ALM is a libre and open source tool for end to end traceability of application and system developments. Prior to version 11.16.99.173 of Community Edition and versions 11.16-6 and 11.15-8 of Enterprise Edition, an attacker with the ability to add one the CI widget to its personal dashboard could execute arbitrary SQL queries. Tuleap Community Edition 11.16.99.173, Tuleap Enterprise Edition 11.16-6, and Tuleap Enterprise Edition 11.15-8 contain a patch for this issue.
Vulnerability ID: CVE-2021-41148
CWE: CWE-89
Severity score: 6.4
Project: https://github.com/Enalean/tuleap
Published: 2021-10-15
File: plugins/hudson/include/HudsonJobWidget.class.php
Commit message: request #15028: The update of the CI job targeted by a widget is vulnerable to blind SQL injections

Change-Id: Ib02a01586740b990feb675a8728a006ea20f7777
Commit author: Thomas Gerbet

Vulnerable version (before fix):
 public function create(Codendi_Request $request)         $vId->required();         if ($request->valid($vId)) {             $job_id = $request->get($this->widget_id . '_job_id');             $sql = 'INSERT INTO plugin_hudson_widget (widget_name, owner_id, owner_type, job_id) VALUES ("' . $this->id . '", ' . $this->owner_id . ", '" . $this->owner_type . "', " . db_escape_int($job_id) . " )";             $res = db_query($sql);             $content_id = db_insertid($res);         }         return $content_id;     }      public function destroy($id)     {         $sql = 'DELETE FROM plugin_hudson_widget WHERE id = ' . $id . ' AND owner_id = ' . $this->owner_id . " AND owner_type = '" . $this->owner_type . "'";         db_query($sql);     }      public function getPreferences($widget_id) public function updatePreferences(Codendi_Request $request)         $request->valid(new Valid_String('cancel'));         if (!$request->exist('cancel')) {             $job_id = $request->get($this->widget_id . '_job_id');             $sql = "UPDATE plugin_hudson_widget SET job_id=" . $job_id . " WHERE owner_id = " . $this->owner_id . " AND owner_type = '" . $this->owner_type . "' AND id = " . (int) $request->get('content_id');             $res = db_query($sql);         }         return true;     } abstract protected function initContent();      */     protected function getJobIdFromWidgetConfiguration()     {         $sql = "SELECT *                     FROM plugin_hudson_widget                     WHERE widget_name = '" . db_es($this->widget_id) . "'                       AND owner_id = " . db_ei($this->owner_id) . "                       AND owner_type = '" . db_es($this->owner_type) . "'                       AND id = " . db_ei($this->content_id);          $res = db_query($sql);         if ($res && db_numrows($res)) {             $data   = db_fetch_array($res);             return $data['job_id'];         }          return null;     } }

Fixed version (after fix):
 public function create(Codendi_Request $request)         $vId->required();         if ($request->valid($vId)) {             $job_id = $request->get($this->widget_id . '_job_id');             $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();             $content_id = (int) $db->insertReturnId(                 'plugin_hudson_widget',                 [                     'widget_name' => $this->id,                     'owner_id'    => $this->owner_id,                     'owner_type'  => $this->owner_type,                     'job_id'      => $job_id                 ]             );         }         return $content_id;     }      public function destroy($id)     {         $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();         $db->run(             'DELETE FROM plugin_hudson_widget WHERE id = ? AND owner_id = ? AND owner_type = ?',             $id,             $this->owner_id,             $this->owner_type,         );     }      public function getPreferences($widget_id) public function updatePreferences(Codendi_Request $request)         $request->valid(new Valid_String('cancel'));         if (!$request->exist('cancel')) {             $job_id = $request->get($this->widget_id . '_job_id');             $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();             $db->run(                 'UPDATE plugin_hudson_widget SET job_id=? WHERE owner_id = ? AND owner_type = ? AND id = ?',                 $job_id,                 $this->owner_id,                 $this->owner_type,                 $this->content_id,             );         }         return true;     } abstract protected function initContent();      */     protected function getJobIdFromWidgetConfiguration()     {         $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();         $job_id = $db->cell(             'SELECT job_id FROM plugin_hudson_widget WHERE widget_name = ? AND owner_id = ? AND owner_type = ? AND id = ?',             $this->widget_id,             $this->owner_id,             $this->owner_type,             $this->content_id,         );          if ($job_id === false) {             return null;         }          return $job_id;     } }

Patch:
patch: @@ -42,17 +42,29 @@ public function create(Codendi_Request $request)
         $vId->required();
         if ($request->valid($vId)) {
             $job_id = $request->get($this->widget_id . '_job_id');
-            $sql = 'INSERT INTO plugin_hudson_widget (widget_name, owner_id, owner_type, job_id) VALUES ("' . $this->id . '", ' . $this->owner_id . ", '" . $this->owner_type . "', " . db_escape_int($job_id) . " )";
-            $res = db_query($sql);
-            $content_id = db_insertid($res);
+            $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();
+            $content_id = (int) $db->insertReturnId(
+                'plugin_hudson_widget',
+                [
+                    'widget_name' => $this->id,
+                    'owner_id'    => $this->owner_id,
+                    'owner_type'  => $this->owner_type,
+                    'job_id'      => $job_id
+                ]
+            );
         }
         return $content_id;
     }
 
     public function destroy($id)
     {
-        $sql = 'DELETE FROM plugin_hudson_widget WHERE id = ' . $id . ' AND owner_id = ' . $this->owner_id . " AND owner_type = '" . $this->owner_type . "'";
-        db_query($sql);
+        $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();
+        $db->run(
+            'DELETE FROM plugin_hudson_widget WHERE id = ? AND owner_id = ? AND owner_type = ?',
+            $id,
+            $this->owner_id,
+            $this->owner_type,
+        );
     }
 
     public function getPreferences($widget_id)
@@ -120,8 +132,14 @@ public function updatePreferences(Codendi_Request $request)
         $request->valid(new Valid_String('cancel'));
         if (!$request->exist('cancel')) {
             $job_id = $request->get($this->widget_id . '_job_id');
-            $sql = "UPDATE plugin_hudson_widget SET job_id=" . $job_id . " WHERE owner_id = " . $this->owner_id . " AND owner_type = '" . $this->owner_type . "' AND id = " . (int) $request->get('content_id');
-            $res = db_query($sql);
+            $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();
+            $db->run(
+                'UPDATE plugin_hudson_widget SET job_id=? WHERE owner_id = ? AND owner_type = ? AND id = ?',
+                $job_id,
+                $this->owner_id,
+                $this->owner_type,
+                $this->content_id,
+            );
         }
         return true;
     }
@@ -133,19 +151,19 @@ abstract protected function initContent();
      */
     protected function getJobIdFromWidgetConfiguration()
     {
-        $sql = "SELECT *
-                    FROM plugin_hudson_widget
-                    WHERE widget_name = '" . db_es($this->widget_id) . "'
-                      AND owner_id = " . db_ei($this->owner_id) . "
-                      AND owner_type = '" . db_es($this->owner_type) . "'
-                      AND id = " . db_ei($this->content_id);
-
-        $res = db_query($sql);
-        if ($res && db_numrows($res)) {
-            $data   = db_fetch_array($res);
-            return $data['job_id'];
+        $db = \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB();
+        $job_id = $db->cell(
+            'SELECT job_id FROM plugin_hudson_widget WHERE widget_name = ? AND owner_id = ? AND owner_type = ? AND id = ?',
+            $this->widget_id,
+            $this->owner_id,
+            $this->owner_type,
+            $this->content_id,
+        );
+
+        if ($job_id === false) {
+            return null;
         }
 
-        return null;
+        return $job_id;
     }
 }
path: plugins/hudson/include/HudsonJobWidget.class.php