Security context
High· 8.8GHSA-j664-qhh4-hpf8 CVE-2023-27898CWE-79Published Mar 10, 2023

Cross-site Scripting vulnerability in Jenkins

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

2.376 → fixed in 2.3940 → fixed in 2.375.4

Details

Jenkins 2.270 through 2.393 (both inclusive), LTS 2.277.1 through 2.375.3 (both inclusive) does not escape the Jenkins version a plugin depends on when rendering the error message stating its incompatibility with the current version of Jenkins, resulting in a stored cross-site scripting (XSS) vulnerability exploitable by attackers able to provide plugins to the configured update sites and have this message shown by Jenkins instances.

The fix

[SECURITY-3037]

Yaroslav Afenkin· Feb 23, 2023, 07:59 AM+172259ac866d99
core/src/main/java/hudson/PluginManager.java+6 2
@@ -1469,7 +1469,11 @@ public HttpResponse doPluginsSearch(@QueryParameter String query, @QueryParamete
jsonObject.put("sourceId", plugin.sourceId);
jsonObject.put("title", plugin.title);
jsonObject.put("displayName", plugin.getDisplayName());
- jsonObject.put("wiki", plugin.wiki);
+ if (plugin.wiki == null || !(plugin.wiki.startsWith("https://") || plugin.wiki.startsWith("http://"))) {
+ jsonObject.put("wiki", StringUtils.EMPTY);
+ } else {
+ jsonObject.put("wiki", plugin.wiki);
+ }
jsonObject.put("categories", plugin.getCategoriesStream()
.filter(PluginManager::isNonMetaLabel)
.map(UpdateCenter::getCategoryDisplayName)
@@ -1486,7 +1490,7 @@ public HttpResponse doPluginsSearch(@QueryParameter String query, @QueryParamete
jsonObject.put("version", plugin.version);
jsonObject.put("popularity", plugin.popularity);
if (plugin.isForNewerHudson()) {
- jsonObject.put("newerCoreRequired", Messages.PluginManager_coreWarning(plugin.requiredCore));
+ jsonObject.put("newerCoreRequired", Messages.PluginManager_coreWarning(Util.xmlEscape(plugin.requiredCore)));
}
if (plugin.hasWarnings()) {
JSONObject unresolvedSecurityWarnings = new JSONObject();
test/src/test/java/hudson/PluginManagerSecurity3037Test.java+101 0
@@ -0,0 +1,101 @@
+package hudson;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.gargoylesoftware.htmlunit.AlertHandler;
+import com.gargoylesoftware.htmlunit.Page;
+import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
+import com.gargoylesoftware.htmlunit.html.HtmlElement;
+import com.gargoylesoftware.htmlunit.html.HtmlElementUtil;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import hudson.model.DownloadService;
+import hudson.model.RootAction;
+import hudson.model.UpdateSite;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.servlet.ServletException;
+import jenkins.model.Jenkins;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.FlagRule;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.TestExtension;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+
+public class PluginManagerSecurity3037Test {
+ @Rule
+ public JenkinsRule r = new JenkinsRule();
+
+ @Rule
+ public FlagRule<Boolean> signatureCheck = new FlagRule<>(() -> DownloadService.signatureCheck, x -> DownloadService.signatureCheck = x);
+
+ @Test
+ public void noInjectionOnAvailablePluginsPage() throws Exception {
+ DownloadService.signatureCheck = false;
+ Jenkins.get().getUpdateCenter().getSites().clear();
+ UpdateSite us = new UpdateSite("Security3037", Jenkins.get().getRootUrl() + "security3037UpdateCenter/update-center.json");
+ Jenkins.get().getUpdateCenter().getSites().add(us);
+
+ try (JenkinsRule.WebClient wc = r.createWebClient()) {
+ HtmlPage p = wc.goTo("pluginManager");
+ List<HtmlElement> elements = p.getElementById("bottom-sticker")
+ .getElementsByTagName("a")
+ .stream()
+ .filter(link -> link.getAttribute("href").equals("checkUpdatesServer"))
+ .collect(Collectors.toList());
+ assertEquals(1, elements.size());
+ AlertHandlerImpl alertHandler = new AlertHandlerImpl();
+ wc.setAlertHandler(alertHandler);
+
+ HtmlElementUtil.click(elements.get(0));
+ HtmlPage available = wc.goTo("pluginManager/available");
+ assertTrue(available.querySelector(".alert-danger")
+ .getTextContent().contains("This plugin is built for Jenkins 2.999"));
+ wc.waitForBackgroundJavaScript(100);
+
+ HtmlAnchor anchor = available.querySelector(".jenkins-table__link");
+ anchor.click(true, false, false);
+ wc.waitForBackgroundJavaScript(100);
+ assertTrue(alertHandler.messages.isEmpty());
+ }
+ }
+
+ static class AlertHandlerImpl implements AlertHandler {
+ List<String> messages = Collections.synchronizedList(new ArrayList<>());
+
+ @Override
+ public void handleAlert(final Page page, final String message) {
+ messages.add(message);
+ }
+ }
+
+ @TestExtension("noInjectionOnAvailablePluginsPage")
+ public static final class Security3037UpdateCenter implements RootAction {
+
+ @Override
+ public String getIconFileName() {
+ return "gear2.png";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "security-3037-update-center";
+ }
+
+ @Override
+ public String getUrlName() {
+ return "security3037UpdateCenter";
+ }
+
+ public void doDynamic(StaplerRequest staplerRequest, StaplerResponse staplerResponse) throws ServletException, IOException {
+ staplerResponse.setContentType("application/json");
+ staplerResponse.setStatus(200);
+ staplerResponse.serveFile(staplerRequest, PluginManagerSecurity3037Test.class.getResource("PluginManagerSecurity3037Test/update-center.json"));
+ }
+ }
+}
test/src/test/resources/hudson/PluginManagerSecurity3037Test/update-center.json+65 0
@@ -0,0 +1,65 @@
+updateCenter.post(
+{
+ "connectionCheckUrl": "https://www.google.com/",
+ "core": {
+ "buildDate": "Jan 31, 2023",
+ "name": "core",
+ "sha1": "TO8l3+wqx2hWtLWMHjmIOj+ERsk=",
+ "sha256": "BrXTCwJse5e6b/shXjv5edRJQRjxfJ/GbNrhgYcrZoU=",
+ "size": 98362341,
+ "url": "https://updates.jenkins.io/download/war/2.389/jenkins.war",
+ "version": "2.389"
+ },
+ "generationTimestamp": "2023-02-03T11:04:30Z",
+ "id": "Security3037",
+ "deprecations": [],
+ "plugins": {
+ "workflow-aggregator": {
+ "buildDate": "Jun 29, 2022",
+ "defaultBranch": "master",
+ "dependencies": [
+ {
+ "name": "pipeline-groovy-lib<img src=x onerror=alert(1)>",
+ "optional": false,
+ "version": "591.v3a_7f422b_d058<img src=x onerror=alert(1)>"
+ }
+ ],
+ "developers": [
+ {
+ "developerId": "johndoe<img src=x onerror=alert(1)>",
+ "name": "john doe<img src=x onerror=alert(1)>"
+ }
+ ],
+ "excerpt": "A suite of plugins that lets you orchestrate automation, simple or complex. See <a href=\"https://www.jenkins.io/solutions/pipeline/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Pipeline as Code with Jenkins</a> for more details.",
+ "gav": "org.jenkins-ci.plugins.workflow:workflow-aggregator:590.v6a_d052e5a_a_b_5<img src=x onerror=alert(1)>",
+ "issueTrackers": [
+ {
+ "reportUrl": "https://www.jenkins.io/participate/report-issue/redirect/#21710<img src=x onerror=alert(1)>",
+ "type": "jira<img src=x onerror=alert(1)>",
+ "viewUrl": "https://issues.jenkins.io/issues/?jql=component=21710<img src=x onerror=alert(1)>"
+ }
+ ],
+ "labels": [
+ "cli<img src=x onerror=alert(1)>",
+ "misc",
+ "slaves",
+ "trigger"
+ ],
+ "name": "workflow-aggregator<img src=x onerror=alert(1)>",
+ "popularity": 256809,
+ "previousTimestamp": "2022-05-24T12:27:03.00Z<img src=x onerror=alert(1)>",
+ "previousVersion": "<img src=x onerror=alert(1)>581.v0c46fa_697ffd",
+ "releaseTimestamp": "2022-06-29T21:02:16.00Z<img src=x onerror=alert(1)>",
+ "requiredCore": "2.999<img src=x onerror=alert(1)>",
+ "scm": "https://github.com/jenkinsci/workflow-aggregator-plugin<img src=x onerror=alert(1)>",
+ "sha1": "6iNlKTnXvr4W+9Svwy1+ctyqgvo=<img src=x onerror=alert(1)>",
+ "sha256": "P0SFgnqOBFZjxkdKORiQUbrhdlfW5YU7tOpe85bfGWE=<img src=x onerror=alert(1)>",
+ "size": 8282,
+ "title": "Pipeline<img src=x onerror=alert(1)>",
+ "url": "https://updates.jenkins.io/download/plugins/workflow-aggregator/590.v6a_d052e5a_a_b_5/workflow-aggregator.hpi",
+ "version": "590.v6a_d052e5a_a_b_5<img src=x onerror=alert(1)>",
+ "wiki": "javascript:alert(1)"
+ }
+ },
+ "updateCenterVersion": "1"
+});

References