CVE-2022-31058 (PHP, published 2022-06-29T18:15Z)
Description: value: Tuleap is a Free & Open Source Suite to improve management of software developments and collaboration. In versions prior to 13.9.99.95 Tuleap does not sanitize properly user inputs when constructing the SQL query to retrieve data for the tracker reports. An attacker with the capability to create a new tracker can execute arbitrary SQL queries. Users are advised to upgrade. There is no known workaround for this issue.
Severity: MEDIUM (CVSS v3 base score: 7.2)
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/agiledashboard/include/AgileDashboard/FormElement/Burnup.php
plugins/testmanagement/include/TestManagement/Step/Execution/Field/StepExecution.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_Alphanum.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_ArtifactId.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_ArtifactLink.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_Burndown.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_Computed.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_CrossReferences.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_Date.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_File.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_LastModifiedBy.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_LastUpdateDate.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_List.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_List_Bind.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_List_Bind_Null.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_List_Bind_Static.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_List_Bind_Ugroups.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_List_Bind_Users.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_Numeric.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_PerTrackerArtifactIdclass.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_PermissionsOnArtifact.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_Priority.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_SubmittedBy.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_SubmittedOn.php
plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_Text.php
plugins/tracker/include/Tracker/Report/Tracker_Report_Renderer_Table.class.php
plugins/tracker_encryption/include/Tracker_FormElement_Field_Encrypted.class.php
Commit message: request #27166: Naming a field "id" breaks the report

Field names are now prefixed in the SQL query in order to avoid conflict
with the static SQL columns we always retrieve.

Change-Id: I7eaaf95e24f52f6e52647d679b5058bc09139b5f


Vulnerable code:
    public function getQuerySelect()
    public function getQuerySelect()
     * @return string
    public function getQuerySelect()
        return "$R.value_id AS `" . $this->name . "`";
    public function getQueryOrderby()
        return '`' . $this->name . '`';
    public function getQueryGroupby()
    public function getQueryGroupby()
        $R1 = 'R1_' . $this->id;
    public function getQuerySelect()
        return "a.id AS `" . $this->name . "`";
    public function getQueryGroupby()
    public function getQuerySelect()
    public function getQuerySelect()
    public function getQuerySelect()
    public function getQuerySelect()
    public function getQuerySelect()
        $R1 = 'R1_' . $this->id;
        return "$R2.value AS `" . $this->name . "`";
    public function getQueryGroupby()
        $R1 = 'R1_' . $this->id;
    public function getQuerySelect()
    public function getQuerySelect()
        return "c.submitted_by AS `" . $this->name . "`";
    public function getQueryGroupby()
    public function getQueryOrderby()
    {
        return $this->name;
    }

    public function getQuerySelect()
        return "c.submitted_on AS `" . $this->name . "`";
    public function getQueryGroupby()
     * @return string
    public function getQuerySelect()
    public function getQueryOrderby()
    public function getQueryGroupby()
     * @return string
    abstract public function getQuerySelect();
    abstract public function getQueryOrderby();
    abstract public function getQueryGroupby();
     * @return string
    public function getQuerySelect()
    public function getQueryOrderby()
    public function getQueryGroupby()
     * @return string
    public function getQuerySelect()
        $R1 = 'R1_' . $this->field->id;
        return "$R2.id AS `" . $this->field->name . "`";
    public function getQueryOrderby()
        $R1 = 'R1_' . $this->field->id;
    public function getQueryGroupby()
        $R1 = 'R1_' . $this->field->id;
     * @return string
    public function getQuerySelect()
        $R1 = 'R1_' . $this->field->id;
        $R3 = 'R3_' . $this->field->id;
        return "$R2.id AS `" . $this->field->name . "`";
    public function getQueryOrderby()
        $uh = UserHelper::instance();
        $R1 = 'R1_' . $this->field->id;
    public function getQueryGroupby()
        $R1 = 'R1_' . $this->field->id;
     * @return string
    public function getQuerySelect()
        $R1 = 'R1_' . $this->field->id;
        $R3 = 'R3_' . $this->field->id;
        return "$R2.user_id AS `" . $this->field->name . "`";
    public function getQueryOrderby()
        $R1 = 'R1_' . $this->field->id;
    public function getQueryGroupby()
        $R1 = 'R1_' . $this->field->id;
    public function getQuerySelect()
        $R1 = 'R1_' . $this->id;
        return "$R2.value AS `" . $this->name . "`";
    public function getQuerySelect()
        return "a.per_tracker_artifact_id AS `" . $this->name . "`";
    public function getQueryGroupby()
     * @return string
    public function getQuerySelect()
     * @return string
    public function getQuerySelect()
        return "R_{$this->id}.rank AS `$this->name`";
    public function getQuerySelect()
        return "a.submitted_by AS `" . $this->name . "`";
    public function getQueryGroupby()
    public function getQueryOrderby()
        return $this->name;
    public function getQuerySelect()
        return "a.submitted_on AS `" . $this->name . "`";
    public function getQueryGroupby()
    public function getQuerySelect()
        $R1 = 'R1_' . $this->id;
        return "$R2.value AS `" . $this->name . "`";
                            $field_name = $column['field']->name;
    public function getQuerySelect()
        return "$R2.value AS `" . $this->name . "`";

Fixed code:
    public function getQuerySelect(): string
        return '';
    public function getQuerySelect(): string
    public const PREFIX_NAME_SQL_COLUMN = 'user_defined_';

    final public function getQuerySelectName(): string
    {
        return \Tuleap\DB\DBFactory::getMainTuleapDBConnection()->getDB()->escapeIdentifier(self::PREFIX_NAME_SQL_COLUMN . $this->name);
    }

    public function getQuerySelect(): string
        return "$R.value_id AS " . $this->getQuerySelectName();
    public function getQueryOrderby(): string
        return $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
        return "a.id AS " . $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
    public function getQuerySelect(): string
        return '';
    public function getQuerySelect(): string
    public function getQuerySelect(): string
    public function getQuerySelect(): string
        return "$R2.value AS " . $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
    public function getQuerySelect(): string
        return "c.submitted_by AS " . $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
        return "c.submitted_on AS " . $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
    public function getQueryOrderby(): string
    public function getQueryGroupby(): string
    abstract public function getQuerySelect(): string;
    abstract public function getQueryOrderby(): string;
    abstract public function getQueryGroupby(): string;
    public function getQuerySelect(): string
    public function getQueryOrderby(): string
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
        return "$R2.id AS " . $this->field->getQuerySelectName();
    public function getQueryOrderby(): string
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
        return "$R2.id AS " . $this->field->getQuerySelectName();
    public function getQueryOrderby(): string
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
        return "$R2.user_id AS " . $this->field->getQuerySelectName();
    public function getQueryOrderby(): string
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
        return "$R2.value AS " . $this->getQuerySelectName();
    public function getQuerySelect(): string
        return "a.per_tracker_artifact_id AS " . $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
    public function getQuerySelect(): string
        return "R_{$this->id}.rank AS " . $this->getQuerySelectName();
    public function getQuerySelect(): string
        return "a.submitted_by AS " . $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQueryOrderby(): string
        return $this->getQuerySelectName();
    public function getQuerySelect(): string
        return "a.submitted_on AS " . $this->getQuerySelectName();
    public function getQueryGroupby(): string
    public function getQuerySelect(): string
        return "$R2.value AS " . $this->getQuerySelectName();
                            $field_name = Tracker_FormElement_Field::PREFIX_NAME_SQL_COLUMN . $column['field']->name;
    public function getQuerySelect(): string
        return "$R2.value AS " . $this->getQuerySelectName();