CVE-2023-28629: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: GoCD is an open source continuous delivery server. GoCD versions before 23.1.0 are vulnerable to a stored XSS vulnerability, where pipeline configuration with a malicious pipeline label configuration can affect browser display of pipeline runs generated from that configuration. An attacker that has permissions to configure GoCD pipelines could include JavaScript elements within the label template, causing a XSS vulnerability to be triggered for any users viewing the Value Stream Map or Job Details for runs of the affected pipeline, potentially allowing them to perform arbitrary actions within the victim's browser context rather than their own. This issue has been fixed in GoCD 23.1.0. Users are advised to upgrade. There are no known workarounds for this vulnerability.
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: 2023-03-27T21:15Z
Repository: https://github.com/gocd/gocd
Language: JavaScript
Files changed: server/src/main/webapp/WEB-INF/rails/app/assets/javascripts/vsm_renderer.js
Commit date: 2022-12-04 16:34:22 +0800
Commit message: Encode VSM node attributes for HTML

Diff: diff --git a/server/src/main/webapp/WEB-INF/rails/app/assets/javascripts/vsm_renderer.js b/server/src/main/webapp/WEB-INF/rails/app/assets/javascripts/vsm_renderer.js
index 0264695723b..9e36d05c61e 100644
--- a/server/src/main/webapp/WEB-INF/rails/app/assets/javascripts/vsm_renderer.js
+++ b/server/src/main/webapp/WEB-INF/rails/app/assets/javascripts/vsm_renderer.js
@@ -373,11 +373,11 @@ Graph_Renderer = function (container) {
         return '<li class="instance">'
                 + '<div title="' + instance.revision + '" class="revision"><span>Revision: </span>' + '<a href="' + instance.locator + '">' + instance.revision + '</a>' + ' </div>'
                 + '<div class="usercomment wraptext">' + parseComment(instance.comment) + '</div>'
                 + '<div class="author">'
                 + '<p>' + _.escape(instance.user) + ' </p>'
-                + '<p>' + instance.modified_time + '</p>'
+                + '<p>' + _.escape(instance.modified_time) + '</p>'
                 + '</div>'
                 + '</li>';
     }
 
     function parseComment(comment) {
@@ -385,14 +385,14 @@ Graph_Renderer = function (container) {
             var comment_markup = "";
             var comment_map = JSON.parse(comment);
             var package_comment = comment_map['COMMENT'];
             var trackback_url = comment_map['TRACKBACK_URL'];
             if (typeof package_comment !== "undefined" || package_comment != null) {
-                comment_markup = package_comment + "<br/>";
+                comment_markup = _.escape(package_comment) + "<br/>";
             }
             if (typeof trackback_url !== "undefined" || trackback_url != null) {
-                return comment_markup + "Trackback: " + "<a href=" + trackback_url + ">" + trackback_url + "</a>";
+                return comment_markup + "Trackback: " + "<a href=" + _.escape(trackback_url) + ">" + _.escape(trackback_url) + "</a>";
             }
             return comment_markup + "Trackback: " + "Not Provided";
         }
         return _.escape(comment);
     }
@@ -402,14 +402,14 @@ Graph_Renderer = function (container) {
             var comment_tooltip = "";
             var comment_map = JSON.parse(comment);
             var package_comment = comment_map['COMMENT'];
             var trackback_url = comment_map['TRACKBACK_URL'];
             if (typeof package_comment !== "undefined" || package_comment != null) {
-                comment_tooltip = package_comment + "\n";
+                comment_tooltip = _.escape(package_comment) + "\n";
             }
             if (typeof trackback_url !== "undefined" || trackback_url != null) {
-                return comment_tooltip + "Trackback: " + trackback_url;
+                return comment_tooltip + "Trackback: " + _.escape(trackback_url);
             }
             return comment_tooltip + "Trackback: " + "Not Provided";
         }
         return _.escape(comment);
     }
@@ -458,30 +458,30 @@ Graph_Renderer = function (container) {
 
     function renderRestrictedPipeline(node) {
         var gui = '';
         gui += '<h3 title="' + node.name + '" class="restricted">' + node.name + '</h3>';
         if (node.message) {
-            gui += '<div class="message restricted"><span>' + node.message + '</span></div>';
+            gui += '<div class="message restricted"><span>' + _.escape(node.message) + '</span></div>';
         }
         gui += '<div class="actions restricted">';
         gui += '<button class="pin" title="Keep dependencies highlighted" /></div>';
         return gui;
     }
 
     function renderWarning(node) {
         var gui = '';
         if (node.message) {
-            gui += '<div class="warning"><span>' + node.message + '</span></div>';
+            gui += '<div class="warning"><span>' + _.escape(node.message) + '</span></div>';
         }
         return gui;
     }
 
     function renderDeletedPipeline(node) {
         var gui = '';
         gui += '<h3 title="' + node.name + '" class="deleted">' + node.name + '</h3>';
         if (node.message) {
-            gui += '<div class="message deleted"><span>' + node.message + '</span></div>';
+            gui += '<div class="message deleted"><span>' + _.escape(node.message) + '</span></div>';
         }
         gui += '<div class="actions deleted"><button class="pin" title="Keep dependencies highlighted" /></div>';
         return gui;
     }
 
@@ -489,14 +489,14 @@ Graph_Renderer = function (container) {
         var gui = '';
         var stagesCount = 0;
         gui += '<li class="instance">';
         if (instance.label != '') {
             if (isCurrent) {
-                gui += '<h4 title="' + instance.label + '"><span class="pipeline_run_label">Instance: ' + instance.label + '</span></h4>';
+                gui += '<h4 title="' + _.escape(instance.label) + '"><span class="pipeline_run_label">Instance: ' + _.escape(instance.label) + '</span></h4>';
             }
             else {
-                gui += '<h4 title="' + instance.label + '"><span class="pipeline_run_label">Instance: ' + instance.label + '</span><span class="vsm_link_wrapper"><a href="' + instance.locator + '">' + 'VSM' + '</a></span></h4>';
+                gui += '<h4 title="' + _.escape(instance.label) + '"><span class="pipeline_run_label">Instance: ' + _.escape(instance.label) + '</span><span class="vsm_link_wrapper"><a href="' + instance.locator + '">' + 'VSM' + '</a></span></h4>';
             }
         }
         if(instance.locator.trim() != "") {
             var duration =  pipelineRunCompleted(instance) ? pipelineRunDuration(instance) : 'In Progress';
             gui += '<span class="duration">Duration: ' + duration + '</span>';

Vulnerable code:                 + '<p>' + instance.modified_time + '</p>'
                comment_markup = package_comment + "<br/>";
                return comment_markup + "Trackback: " + "<a href=" + trackback_url + ">" + trackback_url + "</a>";
                comment_tooltip = package_comment + "\n";
                return comment_tooltip + "Trackback: " + trackback_url;
            gui += '<div class="message restricted"><span>' + node.message + '</span></div>';
            gui += '<div class="warning"><span>' + node.message + '</span></div>';
            gui += '<div class="message deleted"><span>' + node.message + '</span></div>';
                gui += '<h4 title="' + instance.label + '"><span class="pipeline_run_label">Instance: ' + instance.label + '</span></h4>';
                gui += '<h4 title="' + instance.label + '"><span class="pipeline_run_label">Instance: ' + instance.label + '</span><span class="vsm_link_wrapper"><a href="' + instance.locator + '">' + 'VSM' + '</a></span></h4>';
Fixed code:                 + '<p>' + _.escape(instance.modified_time) + '</p>'
                comment_markup = _.escape(package_comment) + "<br/>";
                return comment_markup + "Trackback: " + "<a href=" + _.escape(trackback_url) + ">" + _.escape(trackback_url) + "</a>";
                comment_tooltip = _.escape(package_comment) + "\n";
                return comment_tooltip + "Trackback: " + _.escape(trackback_url);
            gui += '<div class="message restricted"><span>' + _.escape(node.message) + '</span></div>';
            gui += '<div class="warning"><span>' + _.escape(node.message) + '</span></div>';
            gui += '<div class="message deleted"><span>' + _.escape(node.message) + '</span></div>';
                gui += '<h4 title="' + _.escape(instance.label) + '"><span class="pipeline_run_label">Instance: ' + _.escape(instance.label) + '</span></h4>';
                gui += '<h4 title="' + _.escape(instance.label) + '"><span class="pipeline_run_label">Instance: ' + _.escape(instance.label) + '</span><span class="vsm_link_wrapper"><a href="' + instance.locator + '">' + 'VSM' + '</a></span></h4>';