CVE-2021-41148 (PHP, published 2021-10-15T14:15Z)
Description: value: 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.
Severity: MEDIUM (CVSS v3 base score: 8.8)
Weakness: CWE-89 \u2014 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
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.
Repository: https://github.com/Enalean/tuleap
Files changed:
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


Vulnerable code:
            $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);
        $sql = 'DELETE FROM plugin_hudson_widget WHERE id = ' . $id . ' AND owner_id = ' . $this->owner_id . " AND owner_type = '" . $this->owner_type . "'";
        db_query($sql);
            $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);
        $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 code:
            $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
                ]
            );
        $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,
        );
            $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,
            );
        $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;