CVE-2022-31063: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Tuleap is a Free & Open Source Suite to improve management of software developments and collaboration. In versions prior to 13.9.99.111 the title of a document is not properly escaped in the search result of MyDocmanSearch widget and in the administration page of the locked documents. A malicious user with the capability to create a document could force victim to execute uncontrolled code. Users are advised to upgrade. There are no known workarounds for this 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: 2022-06-29T18:15Z
Repository: https://github.com/Enalean/tuleap
Language: PHP
Files changed: plugins/docman/include/Docman_Widget_MyDocmanSearch.class.php, plugins/docman/include/view/Docman_View_Admin_LockInfos.class.php
Commit date: 2022-06-14 10:59:47 +0200
Commit message: request #27173: XSS via the title of a document

Change-Id: Ibdae4792b76c297bf8d553ab9b37f5ae3d76cb2a

Diff: diff --git a/plugins/docman/include/Docman_Widget_MyDocmanSearch.class.php b/plugins/docman/include/Docman_Widget_MyDocmanSearch.class.php
index 047e49bd442f..bfb020ab3a2a 100644
--- a/plugins/docman/include/Docman_Widget_MyDocmanSearch.class.php
+++ b/plugins/docman/include/Docman_Widget_MyDocmanSearch.class.php
@@ -81,11 +81,14 @@ class Docman_Widget_MyDocmanSearch extends Widget
             if ($res) {
                 $dPm      = Docman_PermissionsManager::instance($res['group_id']);
                 $itemPerm = $dPm->userCanAccess($user, $docman_id);
 
                 if ($itemPerm) {
-                    $html .= '<p><a data-test="document-search-link" href="/plugins/docman/?group_id=' . $res['group_id'] . '&action=details&id=' . $docman_id . '&section=properties">Show &quot;' . $res['title'] . '&quot; Properties</a></p>';
+                    $purifier = Codendi_HTMLPurifier::instance();
+                    $html    .= '<p><a data-test="document-search-link" href="/plugins/docman/?group_id=' . urlencode((string) $res['group_id']) . '&action=details&id=' . urlencode((string) $docman_id) . '&section=properties">Show &quot;';
+                    $html    .=  $purifier->purify($res['title']);
+                    $html    .= '&quot; Properties</a></p>';
                     return $html;
                 }
             }
             $html .= '<p data-test="document-search-result">' . dgettext('tuleap-docman', 'You do not have the permission to access the document') . '</p>';
         }
diff --git a/plugins/docman/include/view/Docman_View_Admin_LockInfos.class.php b/plugins/docman/include/view/Docman_View_Admin_LockInfos.class.php
index a9a4cfbb95e2..e2374cb13d11 100644
--- a/plugins/docman/include/view/Docman_View_Admin_LockInfos.class.php
+++ b/plugins/docman/include/view/Docman_View_Admin_LockInfos.class.php
@@ -112,16 +112,20 @@ class Docman_View_Admin_LockInfos extends \Tuleap\Docman\View\Admin\AdminView
                 if ($item === null) {
                     return '</tbody></table>';
                 }
                 $parent   = $dIF->getItemFromDb($item->getParentId());
                 $content .= '<tr>';
-                $content .= '<td>' . '<a href="/plugins/docman/?group_id=' . $params['group_id'] . '&action=details&id=' . $item->getId() . '">' . $item->getTitle() . '</a></td>';
+                $content .= '<td>' . '<a href="/plugins/docman/?group_id=' . urlencode((string) $params['group_id']) . '&action=details&id=' . urlencode((string) $item->getId()) . '">';
+                $content .= $hp->purify($item->getTitle());
+                $content .= '</a></td>';
                 $content .= '<td>';
                 if ($parent === null || $dIF->isRoot($parent)) {
                     $content .= '</td>';
                 } else {
-                    $content .=  '<a href="' . $this->defaultUrl . '&action=show&id=' . $parent->getId() . '">' . $parent->getTitle() . '</a></td>';
+                    $content .=  '<a href="' . $this->defaultUrl . '&action=show&id=' . urlencode((string) $parent->getId()) . '">';
+                    $content .= $hp->purify($parent->getTitle());
+                    $content .= '</a></td>';
                 }
                 $content .= '<td>' . $hp->purify($uH->getDisplayNameFromUserId($row['user_id'])) . '</td>';
                 $content .= '<td>' . format_date($GLOBALS['Language']->getText('system', 'datefmt'), $row['lock_date']) . '</td>';
                 $content .= '</tr>';
             }

Vulnerable code:                     $html .= '<p><a data-test="document-search-link" href="/plugins/docman/?group_id=' . $res['group_id'] . '&action=details&id=' . $docman_id . '&section=properties">Show &quot;' . $res['title'] . '&quot; Properties</a></p>';
                $content .= '<td>' . '<a href="/plugins/docman/?group_id=' . $params['group_id'] . '&action=details&id=' . $item->getId() . '">' . $item->getTitle() . '</a></td>';
                    $content .=  '<a href="' . $this->defaultUrl . '&action=show&id=' . $parent->getId() . '">' . $parent->getTitle() . '</a></td>';
Fixed code:                     $purifier = Codendi_HTMLPurifier::instance();
                    $html    .= '<p><a data-test="document-search-link" href="/plugins/docman/?group_id=' . urlencode((string) $res['group_id']) . '&action=details&id=' . urlencode((string) $docman_id) . '&section=properties">Show &quot;';
                    $html    .=  $purifier->purify($res['title']);
                    $html    .= '&quot; Properties</a></p>';
                $content .= '<td>' . '<a href="/plugins/docman/?group_id=' . urlencode((string) $params['group_id']) . '&action=details&id=' . urlencode((string) $item->getId()) . '">';
                $content .= $hp->purify($item->getTitle());
                $content .= '</a></td>';
                    $content .=  '<a href="' . $this->defaultUrl . '&action=show&id=' . urlencode((string) $parent->getId()) . '">';
                    $content .= $hp->purify($parent->getTitle());
                    $content .= '</a></td>';