CVE-2023-32072: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Tuleap is an open source tool for end to end traceability of application and system developments. Tuleap Community Edition prior to version 14.8.99.60 and Tuleap Enterprise edition prior to 14.8-3 and 14.7-7, the logs of the triggered Jenkins job URLs are not properly escaped. A malicious Git administrator can setup a malicious Jenkins hook to make a victim, also a Git administrator, execute uncontrolled code. Tuleap Community Edition 14.8.99.60, Tuleap Enterprise Edition 14.8-3, and Tuleap Enterprise Edition 14.7-7 contain a patch 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: 4.8
Published: 2023-05-29T21:15Z
Repository: https://github.com/Enalean/tuleap
Language: PHP
Files changed: plugins/hudson_git/include/HudsonGit/GitWebhooksSettingsEnhancer.php, plugins/hudson_git/include/HudsonGit/JenkinsWebhookPresenter.php, plugins/hudson_git/include/hudson_gitPlugin.php
Commit date: 2023-05-05 15:45:25 +0200
Commit message: Fix request #31923 Broken link when more than 10 Jenkins jobs are triggered by one Jenkins hook

Given a Git repository that have a Jenkins webhook configured to trigger Git polling jobs,
and if there are more than 10 polling jobs triggered after a push, then there is a <x more>
returned by Jenkins after the 10th triggered job. This <x more> must not be clickable in the job list.

Change-Id: Ia8283f57c4352a85f65318a34980d028668ae95a

Diff: diff --git a/plugins/hudson_git/include/HudsonGit/GitWebhooksSettingsEnhancer.php b/plugins/hudson_git/include/HudsonGit/GitWebhooksSettingsEnhancer.php
index fa16111593b9..da74d4adbcfb 100644
--- a/plugins/hudson_git/include/HudsonGit/GitWebhooksSettingsEnhancer.php
+++ b/plugins/hudson_git/include/HudsonGit/GitWebhooksSettingsEnhancer.php
@@ -18,19 +18,21 @@
  * along with Tuleap. If not, see <http://www.gnu.org/licenses/>.
  */
 
 namespace Tuleap\HudsonGit;
 
+use Codendi_HTMLPurifier;
 use GitRepository;
 use Tuleap\Git\Webhook\ExternalWebhookPresenter;
 use Tuleap\HudsonGit\Git\Administration\JenkinsServerFactory;
 use Tuleap\HudsonGit\Hook\HookDao;
 use Tuleap\HudsonGit\Hook\ModalsPresenter;
 use Tuleap\Git\Webhook\SectionOfWebhooksPresenter;
 use CSRFSynchronizerToken;
 use TemplateRendererFactory;
 use Tuleap\HudsonGit\Log\LogFactory;
+use Tuleap\Sanitizer\URISanitizer;
 
 /**
  * I am responsible of adding the possibility to repo admin to define jenkins hook for a git repository and
  * display relevant information
  */
@@ -59,10 +61,12 @@ class GitWebhooksSettingsEnhancer
     public function __construct(
         HookDao $dao,
         LogFactory $log_factory,
         CSRFSynchronizerToken $csrf,
         JenkinsServerFactory $jenkins_server_factory,
+        private readonly Codendi_HTMLPurifier $html_purifier,
+        private readonly URISanitizer $uri_sanitizer,
     ) {
         $this->dao                    = $dao;
         $this->csrf                   = $csrf;
         $this->log_factory            = $log_factory;
         $this->jenkins_server_factory = $jenkins_server_factory;
@@ -115,11 +119,13 @@ class GitWebhooksSettingsEnhancer
                 [
                     new JenkinsWebhookPresenter(
                         $repository,
                         $url,
                         $triggered_jobs,
-                        $this->csrf
+                        $this->csrf,
+                        $this->html_purifier,
+                        $this->uri_sanitizer,
                     ),
                 ]
             );
         }
 
diff --git a/plugins/hudson_git/include/HudsonGit/JenkinsWebhookPresenter.php b/plugins/hudson_git/include/HudsonGit/JenkinsWebhookPresenter.php
index 3c9a28bbe00c..c023acd1cd2f 100644
--- a/plugins/hudson_git/include/HudsonGit/JenkinsWebhookPresenter.php
+++ b/plugins/hudson_git/include/HudsonGit/JenkinsWebhookPresenter.php
@@ -24,15 +24,22 @@ use Tuleap\Git\Webhook\GenericWebhookPresenter;
 use Tuleap\Git\Webhook\WebhookLogPresenter;
 use Codendi_HTMLPurifier;
 use GitRepository;
 use CSRFSynchronizerToken;
 use Tuleap\HudsonGit\Log\Log;
+use Tuleap\Sanitizer\URISanitizer;
 
-class JenkinsWebhookPresenter extends GenericWebhookPresenter
+final class JenkinsWebhookPresenter extends GenericWebhookPresenter
 {
-    public function __construct(GitRepository $repository, $url, array $hooklogs, CSRFSynchronizerToken $csrf)
-    {
+    public function __construct(
+        GitRepository $repository,
+        string $url,
+        array $hooklogs,
+        CSRFSynchronizerToken $csrf,
+        private readonly Codendi_HTMLPurifier $html_purifier,
+        private readonly URISanitizer $uri_sanitizer,
+    ) {
         $use_default_edit_modal = false;
         parent::__construct($repository, 'jenkins', $url, [], $csrf, $use_default_edit_modal);
 
         $this->remove_form_action = '/plugins/hudson_git/?group_id=' . (int) $repository->getProjectId();
 
@@ -49,28 +56,31 @@ class JenkinsWebhookPresenter extends GenericWebhookPresenter
     /**
      * @param Log[] $hooklogs
      */
     private function generateHooklogs(array $hooklogs): void
     {
-        $hp = Codendi_HTMLPurifier::instance();
         foreach ($hooklogs as $log) {
             $purified_information = '';
             $job_list             = $log->getJobUrlList();
             if (count($job_list) > 0) {
                 $purified_information .= '<div class="hook-log-triggered-jobs">';
                 $purified_information .= '<h4>' . dgettext("tuleap-hudson_git", "Git plugin triggered jobs:") . '</h4>';
                 foreach ($job_list as $triggered_job_url) {
-                    $purfied_job_url       = $hp->purify($triggered_job_url);
-                    $purified_information .= '<a href="' . $purfied_job_url . '">' . $purfied_job_url . '</a><br>';
+                    $sanitized_job_url = $this->uri_sanitizer->sanitizeForHTMLAttribute($triggered_job_url);
+                    if ($sanitized_job_url !== '' && $triggered_job_url !== '') {
+                        $purified_information .= '<a href="' . $this->html_purifier->purify($sanitized_job_url) . '">' . $this->html_purifier->purify($triggered_job_url) . '</a><br>';
+                    } else {
+                        $purified_information .= $this->html_purifier->purify($triggered_job_url);
+                    }
                 }
                 $purified_information .= '</div>';
             }
 
             if ($log->getStatusCode() !== null) {
                 $purified_information .= '<div class="hook-log-branch-source-status">';
                 $purified_information .= '<h4>' . dgettext("tuleap-hudson_git", "Branch source plugin:") . '</h4>';
-                $purified_information .= $log->getStatusCode();
+                $purified_information .= $this->html_purifier->purify((string) $log->getStatusCode());
                 $purified_information .= '</div>';
             }
 
             $this->hooklogs[] = new WebhookLogPresenter($log->getFormattedPushDate(), $purified_information);
         }
diff --git a/plugins/hudson_git/include/hudson_gitPlugin.php b/plugins/hudson_git/include/hudson_gitPlugin.php
index 4d6c8cbfc262..1defa7533fcf 100644
--- a/plugins/hudson_git/include/hudson_gitPlugin.php
+++ b/plugins/hudson_git/include/hudson_gitPlugin.php
@@ -164,11 +164,13 @@ class hudson_gitPlugin extends Plugin
                         new GitDao(),
                         ProjectManager::instance()
                     )
                 ),
                 $this->getCSRF(),
-                self::getJenkinsServerFactory()
+                self::getJenkinsServerFactory(),
+                Codendi_HTMLPurifier::instance(),
+                new \Tuleap\Sanitizer\URISanitizer(new Valid_HTTPURI()),
             );
             $xzibit->pimp($params);
         }
     }
 

Vulnerable code:                         $this->csrf
class JenkinsWebhookPresenter extends GenericWebhookPresenter
    public function __construct(GitRepository $repository, $url, array $hooklogs, CSRFSynchronizerToken $csrf)
    {
        $hp = Codendi_HTMLPurifier::instance();
                    $purfied_job_url       = $hp->purify($triggered_job_url);
                    $purified_information .= '<a href="' . $purfied_job_url . '">' . $purfied_job_url . '</a><br>';
                $purified_information .= $log->getStatusCode();
                self::getJenkinsServerFactory()
Fixed code: use Codendi_HTMLPurifier;
use Tuleap\Sanitizer\URISanitizer;
        private readonly Codendi_HTMLPurifier $html_purifier,
        private readonly URISanitizer $uri_sanitizer,
                        $this->csrf,
                        $this->html_purifier,
                        $this->uri_sanitizer,
use Tuleap\Sanitizer\URISanitizer;
final class JenkinsWebhookPresenter extends GenericWebhookPresenter
    public function __construct(
        GitRepository $repository,
        string $url,
        array $hooklogs,
        CSRFSynchronizerToken $csrf,
        private readonly Codendi_HTMLPurifier $html_purifier,
        private readonly URISanitizer $uri_sanitizer,
    ) {
                    $sanitized_job_url = $this->uri_sanitizer->sanitizeForHTMLAttribute($triggered_job_url);
                    if ($sanitized_job_url !== '' && $triggered_job_url !== '') {
                        $purified_information .= '<a href="' . $this->html_purifier->purify($sanitized_job_url) . '">' . $this->html_purifier->purify($triggered_job_url) . '</a><br>';
                    } else {
                        $purified_information .= $this->html_purifier->purify($triggered_job_url);
                    }
                $purified_information .= $this->html_purifier->purify((string) $log->getStatusCode());
                self::getJenkinsServerFactory(),
                Codendi_HTMLPurifier::instance(),
                new \Tuleap\Sanitizer\URISanitizer(new Valid_HTTPURI()),