Improper Neutralization of Input During Web Page Generation in Jenkins
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
0 → fixed in 2.176.42.177 → fixed in 2.197
Details
In Jenkins 2.196 and earlier, LTS 2.176.3 and earlier, the f:combobox form control interpreted its item labels as HTML, resulting in a stored XSS vulnerability exploitable by users with permission to define its contents.
The fix
[SECURITY-1525]
test/src/test/java/lib/form/ComboBoxSEC1525Test.java+50 −0
@@ -0,0 +1,50 @@+package lib.form;++import com.gargoylesoftware.htmlunit.html.HtmlElement;+import com.gargoylesoftware.htmlunit.html.HtmlElementUtil;+import com.gargoylesoftware.htmlunit.html.HtmlPage;+import hudson.model.FreeStyleProject;+import hudson.model.Job;+import hudson.util.ComboBoxModel;+import jenkins.model.OptionalJobProperty;+import org.jvnet.hudson.test.HudsonTestCase;+import org.jvnet.hudson.test.Issue;+import org.jvnet.hudson.test.TestExtension;++//TODO meant to be merged back into ComboBoxTest after security release to avoid conflict during the upmerge process+public class ComboBoxSEC1525Test extends HudsonTestCase {+public static class XssProperty extends OptionalJobProperty<Job<?,?>> {+@TestExtension("testEnsureXSSnotPossible")+public static class DescriptorImpl extends OptionalJobProperty.OptionalJobPropertyDescriptor {++@Override+public String getDisplayName() {+return "XSS Property";+}++public ComboBoxModel doFillXssItems() {+return new ComboBoxModel("<h1>HACK</h1>");+}+}+}++@Issue("SECURITY-1525")+public void testEnsureXSSnotPossible() throws Exception {+XssProperty xssProperty = new XssProperty();+FreeStyleProject p = createFreeStyleProject();+p.addProperty(xssProperty);++WebClient wc = new WebClient();++HtmlPage configurePage = wc.getPage(p, "configure");+int numberOfH1Before = configurePage.getElementsByTagName("h1").size();++HtmlElement comboBox = configurePage.getElementByName("_.xss");+HtmlElementUtil.click(comboBox);++// no additional h1, meaning the "payload" is not interpreted+int numberOfH1After = configurePage.getElementsByTagName("h1").size();++assertEquals(numberOfH1Before, numberOfH1After);+}+}
test/src/test/resources/lib/form/ComboBoxSEC1525Test/XssProperty/config.jelly+6 −0
@@ -0,0 +1,6 @@+<?jelly escape-by-default='true'?>+<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">+<f:entry field="xss">+<f:combobox />+</f:entry>+</j:jelly>
war/src/main/webapp/scripts/combobox.js+1 −1
@@ -246,7 +246,7 @@ ComboBox.prototype.populateDropdown = function() {for (var i = 0; i < this.availableItems.length; i++) {var item = document.createElement("div");item.className = "comboBoxItem";-item.innerHTML = this.availableItems[i];+item.innerText = this.availableItems[i];item.id = "item_" + this.availableItems[i];item.comboBox = this;item.comboBoxIndex = i;