CVE-2018-1000604
Description
Persistent XSS in Jenkins Badge Plugin allows attackers with control over badge content to execute JavaScript in another user's browser.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Persistent XSS in Jenkins Badge Plugin allows attackers with control over badge content to execute JavaScript in another user's browser.
Vulnerability
A persisted cross-site scripting (XSS) vulnerability exists in Jenkins Badge Plugin versions 1.4 and earlier. The flaw is located in BadgeSummaryAction.java and HtmlBadgeAction.java, where user-controlled build badge content is rendered without proper sanitization. Attackers who can control build badge content (e.g., through pipeline scripts or job configuration) can inject arbitrary HTML and JavaScript that is stored and later executed when another user views the badge.
Exploitation
An attacker needs the ability to control build badge content, typically granted to users with Job/Configure permission. They inject malicious JavaScript into the badge text or HTML. The injected payload is stored and executed in the victim's browser when the victim performs certain UI actions, such as viewing the build page or interacting with the badge. No further user interaction beyond browsing the affected page is required.
Impact
Successful exploitation allows the attacker to execute arbitrary JavaScript in the context of the victim's browser. This can lead to session hijacking, credential theft, or performing actions on behalf of the victim within Jenkins, potentially gaining administrative access.
Mitigation
The vulnerability is fixed in Badge Plugin version 1.5, released on 2018-06-25. The fix introduces HTML sanitization using the antisamy-markup-formatter library and RawHtmlMarkupFormatter to escape untrusted content. Users should upgrade to version 1.5 or later. As a workaround, restrict the ability to configure jobs to trusted users to limit the attack surface. References: [1], [2], [3].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:badgeMaven | < 1.5 | 1.5 |
Affected products
1Patches
15 files changed · +63 −5
pom.xml+5 −0 modified@@ -112,6 +112,11 @@ <version>1.39</version> </dependency> + <dependency> + <groupId>org.jenkins-ci.plugins</groupId> + <artifactId>antisamy-markup-formatter</artifactId> + <version>1.5</version> + </dependency> <dependency> <groupId>org.jenkins-ci.plugins.workflow</groupId> <artifactId>workflow-cps</artifactId>
src/main/java/com/jenkinsci/plugins/badge/action/BadgeSummaryAction.java+17 −1 modified@@ -23,14 +23,21 @@ */ package com.jenkinsci.plugins.badge.action; +import hudson.markup.RawHtmlMarkupFormatter; import org.apache.commons.lang.StringEscapeUtils; import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + + @ExportedBean(defaultVisibility = 2) public class BadgeSummaryAction extends AbstractAction { private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(BadgeSummaryAction.class.getName()); private final String iconPath; private String summaryText = ""; @@ -57,9 +64,18 @@ public String getIconPath() { return iconPath; } + public String getRawText() { + return summaryText; + } + @Exported public String getText() { - return summaryText; + try { + return new RawHtmlMarkupFormatter(false).translate(summaryText); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Error preparing summary text for ui", e); + return "<b><font color=\"red\">ERROR</font></b>"; + } } @Whitelisted
src/main/java/com/jenkinsci/plugins/badge/action/HtmlBadgeAction.java+17 −2 modified@@ -23,12 +23,19 @@ */ package com.jenkinsci.plugins.badge.action; +import hudson.markup.RawHtmlMarkupFormatter; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + @ExportedBean(defaultVisibility = 2) public class HtmlBadgeAction extends AbstractBadgeAction { private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(BadgeSummaryAction.class.getName()); + private final String html; private HtmlBadgeAction(String html) { @@ -52,9 +59,17 @@ public String getIconFileName() { return null; } - @Exported - public String getHtml() { + public String getRawHtml() { return html; } + @Exported + public String getHtml() { + try { + return new RawHtmlMarkupFormatter(false).translate(html); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Error preparing html content for ui", e); + return "<b><font color=\"red\">ERROR</font></b>"; + } + } }
src/test/java/com/jenkinsci/plugins/badge/dsl/AddHtmlBadgeStepTest.java+14 −1 modified@@ -40,6 +40,18 @@ public class AddHtmlBadgeStepTest extends AbstractBadgeTest { @Test public void addHtmlBadge() throws Exception { String html = UUID.randomUUID().toString(); + testAddHtmlBadge(html, html); + } + + @Test + public void addHtmlBadge_remove_script() throws Exception { + String uuid = UUID.randomUUID().toString(); + String html = uuid + "<script>alert('exploit!');</script>"; + testAddHtmlBadge(html, uuid); + } + + + private void testAddHtmlBadge(String html, String expected) throws Exception { WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); String script = "addHtmlBadge(\"" + html + "\")"; @@ -51,6 +63,7 @@ public void addHtmlBadge() throws Exception { assertEquals(1, badgeActions.size()); HtmlBadgeAction action = (HtmlBadgeAction) badgeActions.get(0); - assertEquals(html, action.getHtml()); + assertEquals(expected, action.getHtml()); + assertEquals(html, action.getRawHtml()); } }
src/test/java/com/jenkinsci/plugins/badge/dsl/CreateSummaryStepTest.java+10 −1 modified@@ -51,6 +51,15 @@ public void createSummary_html_unescaped() throws Exception { assertEquals("<li>" + text + "</li>", action.getText()); } + @Test + public void createSummary_html_unescaped_remove_script() throws Exception { + String text = randomUUID().toString(); + String html = "<li>" + text + "</li><script>alert(\"exploit!\");</script>"; + BadgeSummaryAction action = createSummary("summary.appendText('" + html + "', false);"); + assertEquals("<li>" + text + "</li>", action.getText()); + assertEquals(html, action.getRawText()); + } + @Test public void createSummary_html_escaped() throws Exception { String text = randomUUID().toString(); @@ -85,7 +94,7 @@ public void createSummary_with_text() throws Exception { String text = randomUUID().toString(); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); - p.setDefinition(new CpsFlowDefinition("def summary = createSummary(icon:\"" + icon + "\", text:\""+text+"\")", true)); + p.setDefinition(new CpsFlowDefinition("def summary = createSummary(icon:\"" + icon + "\", text:\"" + text + "\")", true)); WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); List<BadgeSummaryAction> summaryActions = b.getActions(BadgeSummaryAction.class); assertEquals(1, summaryActions.size());
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-3xjq-8j89-xrw9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-1000604ghsaADVISORY
- github.com/jenkinsci/badge-plugin/commit/63a7744cef33338e62898576a50bcc521d76ba9fghsaWEB
- jenkins.io/security/advisory/2018-06-25/ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.