CVE-2021-41155 (PHP, published 2021-10-18T22:15Z)
Description: value: Tuleap is a Free & Open Source Suite to improve management of software developments and collaboration. In affected versions Tuleap does not sanitize properly user inputs when constructing the SQL query to browse and search revisions in the CVS repositories. The following versions contain the fix: Tuleap Community Edition 11.17.99.146, Tuleap Enterprise Edition 11.17-5, Tuleap Enterprise Edition 11.16-7.
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:
src/www/cvs/browse_commit.php
src/www/cvs/commit_utils.php
Commit message: request #16214: SQL injection in CVS revisions browser

Also fixes a reflected XSS via the same injection point.

Change-Id: I4e4d5132e748f57db46f4b685cc18e577ed5496f


Vulnerable code:
$order_by = '';
        $order_by = ' ORDER BY ' . commit_criteria_list_to_query($morder);
// Transform criteria list to SQL query (+ means ascending
// - is descending)
function commit_criteria_list_to_query($criteria_list)
    $criteria_list = str_replace('>', ' ASC', $criteria_list);
    $criteria_list = str_replace('<', ' DESC', $criteria_list);

    return $criteria_list;
            $arr_text[] = '<a href="' . $url . '&morder=' . $morder . '#results">' .
            commit_field_get_label($attr) . '</a><img src="' . util_get_dir_image_theme() .
function cvs_get_revisions(&$project, $offset, $chunksz, $_tag = 100, $_branch = 100, $_commit_id = '', $_commiter = 100, $_srch = '', $order_by = '', $pv = 0)
    if (! $order_by) {
        $order_by = " ORDER BY id desc, f_when desc ";
    $order_by .

Fixed code:
$order_by = [];
        $order_by = commit_criteria_list_to_query($morder);
/**
 * @psalm-return array<array{order:"ASC"|"DESC", column: "id"|"revision"|"did"|"description"|"c_when"|"date"|"f_when"|"who"}>
 */
function commit_criteria_list_to_query(string $criteria_list): array
    $order_list = [];
    foreach (explode(',', $criteria_list) as $criteria) {
        if (preg_match('/^(?<column>[a-z]+)(?<order>[<>]?)$/', $criteria, $matches) === 1) {
            $column = $matches['column'];
            if (! in_array($column, ['id', 'revision', 'did', 'description', 'c_when', 'date', 'f_when', 'who'], true)) {
                continue;
            }
            $order = 'ASC';
            if ($matches['order'] === '<') {
                $order = 'DESC';
            }
            $order_list[] = ['order' => $order, 'column' => $column];
        }
    }
    return $order_list;
        $purifier = Codendi_HTMLPurifier::instance();

            $arr_text[] = '<a href="' . $url . '&morder=' . $purifier->purify(urlencode($morder)) . '#results">' .
            $purifier->purify(commit_field_get_label($attr)) . '</a><img src="' . util_get_dir_image_theme() .
/**
 * @psalm-param array<array{order:"ASC"|"DESC", column: "id"|"revision"|"did"|"description"|"c_when"|"date"|"f_when"|"who"}> $order_by
 */
function cvs_get_revisions($project, $offset, $chunksz, $_tag = 100, $_branch = 100, $_commit_id = '', $_commiter = 100, $_srch = '', array $order_by = [], $pv = 0)
    if (empty($order_by)) {
        $order_by_sql = ' ORDER BY id desc, f_when desc ';
    } else {
        $order_by_sql = ' ORDER BY ';
        $order_by_sql .= implode(
            ',',
            array_map(
                static function (array $order_by_row) {
                    return $order_by_row['column'] . ' ' . $order_by_row['order'];
                },
                $order_by
            )
        );
        $order_by_sql .= ' ';
    $order_by_sql .