CVE-2017-2601
Description
Jenkins before 2.44/2.32.2 has stored XSS via job parameter names and descriptions, allowing attackers with job configuration permission to inject arbitrary JavaScript.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Jenkins before 2.44/2.32.2 has stored XSS via job parameter names and descriptions, allowing attackers with job configuration permission to inject arbitrary JavaScript.
Vulnerability
Jenkins versions before 2.44 and 2.32.2 include a stored cross-site scripting (XSS) vulnerability in parameter names and descriptions [4]. Users with the "Configure" permission on a job can inject arbitrary JavaScript code into these fields, which is then executed when a user views the job configuration page [4].
Exploitation
An attacker must have the permission to configure jobs in Jenkins. By crafting a parameter name or description field containing malicious JavaScript, the payload is stored and executed when other users (including administrators) view the affected job's configuration page [4]. No additional user interaction beyond viewing the page is required.
Impact
Successful exploitation leads to arbitrary JavaScript execution in the context of the victim's browser, potentially allowing the attacker to perform actions on the Jenkins server with the victim's privileges, such as modifying job configurations, triggering builds, or exfiltrating sensitive information [4].
Mitigation
The vulnerability is fixed in Jenkins versions 2.44 and 2.32.2 [4]. Users should upgrade to these versions or later. No known workarounds exist for earlier versions.
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.main:jenkins-coreMaven | < 2.32.2 | 2.32.2 |
org.jenkins-ci.main:jenkins-coreMaven | >= 2.34, < 2.44 | 2.44 |
Affected products
2- unspecified/jenkinsv5Range: jenkins 2.44
Patches
1fd2e081b9471Merge pull request #95 from jenkinsci-cert/SECURITY-353
18 files changed · +119 −26
core/src/main/java/hudson/model/ParameterValue.java+18 −0 modified@@ -31,11 +31,16 @@ import hudson.tasks.BuildWrapper; import hudson.tasks.Builder; import hudson.util.VariableResolver; +import java.io.IOException; import java.io.Serializable; import java.util.Map; +import java.util.logging.Logger; +import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.export.Exported; @@ -70,6 +75,9 @@ */ @ExportedBean(defaultVisibility=3) public abstract class ParameterValue implements Serializable { + + private static final Logger LOGGER = Logger.getLogger(ParameterValue.class.getName()); + protected final String name; private String description; @@ -91,6 +99,16 @@ public void setDescription(String description) { this.description = description; } + @Restricted(DoNotUse.class) // for value.jelly + public String getFormattedDescription() { + try { + return Jenkins.getInstance().getMarkupFormatter().translate(description); + } catch (IOException e) { + LOGGER.warning("failed to translate description using configured markup formatter"); + return ""; + } + } + /** * Name of the parameter. *
core/src/main/resources/hudson/model/BooleanParameterDefinition/index.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.formattedDescription}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <div name="parameter"> <input type="hidden" name="name" value="${it.name}" /> <f:checkbox name="value" checked="${it.defaultValue}" />
core/src/main/resources/hudson/model/BooleanParameterValue/value.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.description}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <f:checkbox name="value" checked="${it.value}" readonly="true" /> </f:entry> </j:jelly> \ No newline at end of file
core/src/main/resources/hudson/model/ChoiceParameterDefinition/index.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.formattedDescription}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <div name="parameter"> <input type="hidden" name="name" value="${it.name}" /> <select name="value">
core/src/main/resources/hudson/model/FileParameterDefinition/index.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.formattedDescription}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <div name="parameter"> <input type="hidden" name="name" value="${it.name}" /> <input name="file" type="file" jsonAware="true" />
core/src/main/resources/hudson/model/FileParameterValue/value.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <j:if test="${it.originalFileName != null}"> <j:invokeStatic var="encodedName" className="hudson.Util" method="rawEncode"> <j:arg value="${it.name}" />
core/src/main/resources/hudson/model/ParametersAction/index.jelly+1 −0 modified@@ -36,6 +36,7 @@ THE SOFTWARE. <l:main-panel> <h1>${%Build} ${build.displayName}</h1> <l:pane title="${%Parameters}" width="3"> + <j:set var="escapeEntryTitleAndDescription" value="true"/> <!-- SECURITY-353 defense unless overridden --> <j:forEach var="parameterValue" items="${it.parameters}"> <st:include it="${parameterValue}" page="value.jelly" />
core/src/main/resources/hudson/model/ParametersDefinitionProperty/index.jelly+1 −0 modified@@ -45,6 +45,7 @@ THE SOFTWARE. <f:form method="post" action="build${empty(delay)?'':'?delay='+delay}" name="parameters" tableClass="parameters"> <j:forEach var="parameterDefinition" items="${it.parameterDefinitions}"> + <j:set var="escapeEntryTitleAndDescription" value="true"/> <!-- SECURITY-353 defense unless overridden --> <tbody> <st:include it="${parameterDefinition}" page="${parameterDefinition.descriptor.valuePage}" />
core/src/main/resources/hudson/model/PasswordParameterDefinition/index.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.formattedDescription}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <div name="parameter"> <input type="hidden" name="name" value="${it.name}" /> <f:password name="value" value="${it.defaultValueAsSecret}" />
core/src/main/resources/hudson/model/PasswordParameterValue/value.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.description}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <f:password name="value" value="********" readonly="true" /> </f:entry> </j:jelly> \ No newline at end of file
core/src/main/resources/hudson/model/RunParameterDefinition/index.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.formattedDescription}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <div name="parameter"> <input type="hidden" name="name" value="${it.name}" /> <select name="runId">
core/src/main/resources/hudson/model/RunParameterValue/value.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.description}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <a href="${rootURL}/${it.run.url}" class="model-link inside">${it.run.fullDisplayName}</a> </f:entry> </j:jelly>
core/src/main/resources/hudson/model/StringParameterDefinition/index.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.formattedDescription}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <div name="parameter"> <input type="hidden" name="name" value="${it.name}" /> <f:textbox name="value" value="${it.defaultValue}" />
core/src/main/resources/hudson/model/StringParameterValue/value.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.description}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <f:textbox name="value" value="${it.value}" readonly="true" /> </f:entry> </j:jelly> \ No newline at end of file
core/src/main/resources/hudson/model/TextParameterDefinition/index.jelly+2 −1 modified@@ -24,7 +24,8 @@ THE SOFTWARE. --> <?jelly escape-by-default='true'?> <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.formattedDescription}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <div name="parameter"> <input type="hidden" name="name" value="${it.name}"/> <f:textarea name="value" value="${it.defaultValue}"/>
core/src/main/resources/hudson/model/TextParameterValue/value.jelly+2 −1 modified@@ -26,7 +26,8 @@ THE SOFTWARE. <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project"> - <f:entry title="${it.name}" description="${it.description}"> + <j:set var="escapeEntryTitleAndDescription" value="false"/> + <f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}"> <f:textarea name="value" value="${it.value}" readonly="readonly" /> </f:entry> </j:jelly> \ No newline at end of file
core/src/main/resources/lib/form/entry.jelly+6 −2 modified@@ -32,6 +32,8 @@ THE SOFTWARE. <st:attribute name="title"> Name of the entry. Think of this like a label for the control. + + This content is HTML (unless the boolean variable escapeEntryTitleAndDescription is set). Use h.escape if necessary. </st:attribute> <st:attribute name="field"> Used for the databinding. TBD. When this attribute @@ -46,6 +48,8 @@ THE SOFTWARE. This text shouldn't get too long, and in recent Hudson, this feature is somewhat de-emphasized, in favor of the inline foldable help page specified via @help. + + This content is HTML (unless the boolean variable escapeEntryTitleAndDescription is set). Use h.escape if necessary. </st:attribute> <st:attribute name="help"> URL to the HTML page. When this attribute is specified, the entry gets @@ -67,7 +71,7 @@ THE SOFTWARE. <tr> <td class="setting-leftspace"><st:nbsp/></td> <td class="setting-name"> - <j:out value="${attrs.title}" /> + <j:out value="${escapeEntryTitleAndDescription ? h.escape(attrs.title) : attrs.title}" /> </td> <td class="setting-main"> <d:invokeBody /> @@ -78,7 +82,7 @@ THE SOFTWARE. <tr class="validation-error-area"><td colspan="2" /><td /><td /></tr> <j:if test="${!empty(attrs.description)}"> <f:description> - <j:out value="${description}"/> + <j:out value="${escapeEntryTitleAndDescription ? h.escape(attrs.description) : attrs.description}"/> </f:description> </j:if> <j:if test="${attrs.help!=null}">
test/src/test/java/hudson/model/ParametersTest.java+67 −11 modified@@ -1,25 +1,30 @@ package hudson.model; -import static org.junit.Assert.*; - import com.gargoylesoftware.htmlunit.html.DomNodeUtil; +import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlFormUtil; -import org.junit.Rule; -import org.junit.Test; - +import com.gargoylesoftware.htmlunit.html.HtmlOption; import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlForm; -import com.gargoylesoftware.htmlunit.html.HtmlElement; import com.gargoylesoftware.htmlunit.html.HtmlTextInput; -import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput; -import com.gargoylesoftware.htmlunit.html.HtmlOption; +import hudson.markup.MarkupFormatter; +import java.io.IOException; +import java.io.Writer; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.http.HttpStatus; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; import org.jvnet.hudson.test.CaptureEnvironmentBuilder; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule.WebClient; -import java.util.Set; - /** * @author huybrechts */ @@ -28,6 +33,9 @@ public class ParametersTest { @Rule public JenkinsRule j = new JenkinsRule(); + @Rule + public ErrorCollector collector = new ErrorCollector(); + @Test public void parameterTypes() throws Exception { FreeStyleProject otherProject = j.createFreeStyleProject(); @@ -216,4 +224,52 @@ public void unicodeParametersArePresetCorrectly() throws Exception { final HtmlForm form = page.getFormByName("parameters"); HtmlFormUtil.submit(form, HtmlFormUtil.getButtonByCaption(form, "Build")); } + + @Issue("SECURITY-353") + @Test + public void xss() throws Exception { + j.jenkins.setMarkupFormatter(new MyMarkupFormatter()); + FreeStyleProject p = j.createFreeStyleProject("p"); + StringParameterDefinition param = new StringParameterDefinition("<param name>", "<param default>", "<param description>"); + assertEquals("<b>[</b>param description<b>]</b>", param.getFormattedDescription()); + p.addProperty(new ParametersDefinitionProperty(param)); + WebClient wc = j.createWebClient(); + wc.getOptions().setThrowExceptionOnFailingStatusCode(false); + HtmlPage page = wc.getPage(p, "build?delay=0sec"); + collector.checkThat(page.getWebResponse().getStatusCode(), is(HttpStatus.SC_METHOD_NOT_ALLOWED)); // 405 to dissuade scripts from thinking this triggered the build + String text = page.getWebResponse().getContentAsString(); + collector.checkThat("build page should escape param name", text, containsString("<param name>")); + collector.checkThat("build page should not leave param name unescaped", text, not(containsString("<param name>"))); + collector.checkThat("build page should escape param default", text, containsString("<param default>")); + collector.checkThat("build page should not leave param default unescaped", text, not(containsString("<param default>"))); + collector.checkThat("build page should mark up param description", text, containsString("<b>[</b>param description<b>]</b>")); + collector.checkThat("build page should not leave param description unescaped", text, not(containsString("<param description>"))); + HtmlForm form = page.getFormByName("parameters"); + HtmlTextInput value = form.getInputByValue("<param default>"); + value.setText("<param value>"); + j.submit(form); + j.waitUntilNoActivity(); + FreeStyleBuild b = p.getBuildByNumber(1); + page = j.createWebClient().getPage(b, "parameters/"); + text = page.getWebResponse().getContentAsString(); + collector.checkThat("parameters page should escape param name", text, containsString("<param name>")); + collector.checkThat("parameters page should not leave param name unescaped", text, not(containsString("<param name>"))); + collector.checkThat("parameters page should escape param value", text, containsString("<param value>")); + collector.checkThat("parameters page should not leave param value unescaped", text, not(containsString("<param value>"))); + collector.checkThat("parameters page should mark up param description", text, containsString("<b>[</b>param description<b>]</b>")); + collector.checkThat("parameters page should not leave param description unescaped", text, not(containsString("<param description>"))); + } + static class MyMarkupFormatter extends MarkupFormatter { + @Override + public void translate(String markup, Writer output) throws IOException { + Matcher m = Pattern.compile("[<>]").matcher(markup); + StringBuffer buf = new StringBuffer(); + while (m.find()) { + m.appendReplacement(buf, m.group().equals("<") ? "<b>[</b>" : "<b>]</b>"); + } + m.appendTail(buf); + output.write(buf.toString()); + } + } + }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
12- github.com/advisories/GHSA-r69c-5j7c-vm6qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2017-2601ghsaADVISORY
- www.openwall.com/lists/oss-security/2022/04/12/5ghsamailing-listWEB
- www.openwall.com/lists/oss-security/2022/05/17/8ghsamailing-listWEB
- www.openwall.com/lists/oss-security/2022/06/22/3ghsamailing-listWEB
- www.openwall.com/lists/oss-security/2022/06/30/3ghsamailing-listWEB
- www.openwall.com/lists/oss-security/2022/10/19/3ghsamailing-listWEB
- www.securityfocus.com/bid/95960ghsavdb-entryWEB
- bugzilla.redhat.com/show_bug.cgighsaWEB
- github.com/jenkinsci/jenkins/commit/fd2e081b947124c90bcd97bfc55e1a7f2ef41a74ghsaWEB
- jenkins.io/security/advisory/2017-02-01ghsaWEB
- jenkins.io/security/advisory/2017-02-01/mitre
News mentions
2- Jenkins Security Advisory 2022-05-17Jenkins Security Advisories · May 17, 2022
- Jenkins Security Advisory 2022-04-12Jenkins Security Advisories · Apr 12, 2022