CVE-2021-41142: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Tuleap Open ALM is a libre and open source tool for end to end traceability of application and system developments. There is a cross-site scripting vulnerability in Tuleap Community Edition prior to 12.11.99.25 and Tuleap Enterprise Edition 12.11-2. A malicious user with the capability to add and remove attachment to an artifact could force a victim to execute uncontrolled code. Tuleap Community Edition 11.17.99.146 and Tuleap Enterprise Edition 12.11-2 contain a fix for the issue.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2021-10-14T16:15Z
Repository: https://github.com/Enalean/tuleap
Language: PHP
Files changed: plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetValue_File.class.php, plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_File.php
Commit date: 2021-07-26 13:46:58 +0200
Commit message: request #22570: XSS via the name of a deleted attachment

Change-Id: I3b7289c719ed8eacb836237fc186f59898b627bc

Diff: diff --git a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetValue_File.class.php b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetValue_File.class.php
index 5eeae799625..37b24d7b884 100644
--- a/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetValue_File.class.php
+++ b/plugins/tracker/include/Tracker/Artifact/Tracker_Artifact_ChangesetValue_File.class.php
@@ -240,11 +240,12 @@ class Tracker_Artifact_ChangesetValue_File extends Tracker_Artifact_ChangesetVal
             $removed = [];
             foreach (array_diff($changeset_value->getFiles(), $this->files) as $fi) {
                 $removed[] = $fi->getFilename();
             }
             if ($removed = implode(', ', $removed)) {
-                $result .= $removed . ' ' . dgettext('tuleap-tracker', 'removed');
+                $purifier = Codendi_HTMLPurifier::instance();
+                $result  .= $purifier->purify($removed) . ' ' . dgettext('tuleap-tracker', 'removed');
             }
 
             $added = $this->fetchAddedFiles(array_diff($this->files, $changeset_value->getFiles()), $format, $is_for_mail);
             if ($added && $result) {
                 $result .= $format === 'html' ? '; ' : PHP_EOL;
diff --git a/plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_File.php b/plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_File.php
index 7fd0f35a245..2f86cea009e 100644
--- a/plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_File.php
+++ b/plugins/tracker/include/Tracker/FormElement/Tracker_FormElement_Field_File.php
@@ -308,11 +308,11 @@ class Tracker_FormElement_Field_File extends Tracker_FormElement_Field
         if ($values !== null && count($values) > 0) {
             $hp    = Codendi_HTMLPurifier::instance();
             $uh    = UserHelper::instance();
             $added = [];
             foreach ($values as $fileinfo) {
-                $query_link            = $this->getFileHTMLUrl($fileinfo);
+                $query_link            = $hp->purify($this->getFileHTMLUrl($fileinfo));
                 $sanitized_description = $hp->purify($fileinfo->getDescription(), CODENDI_PURIFIER_CONVERT_HTML);
 
                 $link_show = '<a href="' . $query_link . '"' .
                                  $this->getVisioningAttributeForLink($fileinfo, $read_only, $lytebox_id) . '
                                  title="' . $sanitized_description . '">';
@@ -324,19 +324,19 @@ class Tracker_FormElement_Field_File extends Tracker_FormElement_Field
 
                 $add .= '<div class="tracker_artifact_preview_attachment_hover">';
                 if ($submitter_needed) {
                     $add .= '<div class="tracker_artifact_attachment_submitter">' . 'By ' . $uh->getLinkOnUserFromUserId($fileinfo->getSubmittedBy()) . '</div>';
                 }
-                $add .= '<div class="tracker_artifact_attachment_size">(' . $fileinfo->getHumanReadableFilesize() . ')</div>';
+                $add .= '<div class="tracker_artifact_attachment_size">(' . $hp->purify($fileinfo->getHumanReadableFilesize()) . ')</div>';
                 $add .= '<div>';
                 $add .= $link_show . '<i class="fa fa-eye"></i></a>';
                 $add .= '<a href="' . $query_link . '" download><i class="fa fa-download"></i></a>';
                 $add .= '</div>';
                 $add .= '</div>';
 
                 if ($fileinfo->isImage()) {
-                    $query_add = $this->getFileHTMLPreviewUrl($fileinfo);
+                    $query_add = $hp->purify($this->getFileHTMLPreviewUrl($fileinfo));
 
                     $add .= '<div class="tracker_artifact_preview_attachment image">';
                     $add .= '<div style="background-image: url(\'' . $query_add . '\')"></div>';
                     $add .= '</div>';
                 } else {
@@ -506,17 +506,17 @@ class Tracker_FormElement_Field_File extends Tracker_FormElement_Field
             }
         } else {
             $hp    = Codendi_HTMLPurifier::instance();
             $added = [];
             foreach ($values as $fileinfo) {
-                $query_link            = $this->getFileHTMLUrl($fileinfo);
+                $query_link            = $hp->purify($this->getFileHTMLUrl($fileinfo));
                 $sanitized_description = $hp->purify($fileinfo->getDescription(), CODENDI_PURIFIER_CONVERT_HTML);
                 $link_show             = '<a href="' . $url . $query_link . '"
                                  title="' . $sanitized_description . '">';
 
                 $info  = $link_show . $hp->purify($fileinfo->getFilename(), CODENDI_PURIFIER_CONVERT_HTML) . '</a>';
-                $info .= ' (' . $fileinfo->getHumanReadableFilesize() . ')';
+                $info .= ' (' . $hp->purify($fileinfo->getHumanReadableFilesize()) . ')';
 
                 $add     = '<div class="tracker_artifact_attachment">';
                 $add    .= '<table><tr><td>';
                 $add    .= $info;
                 $add    .= '</td></tr></table>';
@@ -658,11 +658,11 @@ class Tracker_FormElement_Field_File extends Tracker_FormElement_Field
                 foreach ($files_info as $file_info) {
                     $add = '';
 
                     if ($file_info->isImage()) {
                         $query = $this->getFileHTMLPreviewUrl($file_info);
-                        $add  .= '<img src="' . $query . '"
+                        $add  .= '<img src="' . $hp->purify($query) . '"
                                       alt="' .  $hp->purify($file_info->getDescription(), CODENDI_PURIFIER_CONVERT_HTML)  . '"
                                  >';
                     } elseif ($file_info->getDescription()) {
                         $add .= '<div class="cross-ref-tooltip-collection-item">';
                         $add .= '<i class="fa fa-paperclip"></i>';

Vulnerable code:                 $result .= $removed . ' ' . dgettext('tuleap-tracker', 'removed');
                $query_link            = $this->getFileHTMLUrl($fileinfo);
                $add .= '<div class="tracker_artifact_attachment_size">(' . $fileinfo->getHumanReadableFilesize() . ')</div>';
                    $query_add = $this->getFileHTMLPreviewUrl($fileinfo);
                $query_link            = $this->getFileHTMLUrl($fileinfo);
                $info .= ' (' . $fileinfo->getHumanReadableFilesize() . ')';
                        $add  .= '<img src="' . $query . '"
Fixed code:                 $purifier = Codendi_HTMLPurifier::instance();
                $result  .= $purifier->purify($removed) . ' ' . dgettext('tuleap-tracker', 'removed');
                $query_link            = $hp->purify($this->getFileHTMLUrl($fileinfo));
                $add .= '<div class="tracker_artifact_attachment_size">(' . $hp->purify($fileinfo->getHumanReadableFilesize()) . ')</div>';
                    $query_add = $hp->purify($this->getFileHTMLPreviewUrl($fileinfo));
                $query_link            = $hp->purify($this->getFileHTMLUrl($fileinfo));
                $info .= ' (' . $hp->purify($fileinfo->getHumanReadableFilesize()) . ')';
                        $add  .= '<img src="' . $hp->purify($query) . '"