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
Details
Jenkins 2.227 and earlier, LTS 2.204.5 and earlier served files uploaded as file parameters to a build without specifying appropriate `Content-Security-Policy HTTP` headers. This resulted in a stored cross-site scripting (XSS) vulnerability exploitable by users with permissions to build a job with file parameters.\n\nJenkins now sets `Content-Security-Policy` HTTP headers when serving files uploaded via a file parameter to the same value as used for files in workspaces and archived artifacts not served using the Resource Root URL.\n\nThe system property `hudson.model.DirectoryBrowserSupport.CSP` can be set to override the value of `Content-Security-Policy` headers sent when serving these files. This is the same system property used for files in workspaces and archived artifacts unless those are served via the [Resource Root URL](https://www.jenkins.io/doc/upgrade-guide/2.204/#resource-domain-support) and works the same way for file parameters. See [Configuring Content Security Policy](https://www.jenkins.io/doc/book/security/configuring-content-security-policy) to learn more.\n\nEven when Jenkins is configured to serve files in workspaces and archived artifacts using the Resource Root URL (introduced in Jenkins 2.200), file parameters are not, and therefore still subject to `Content-Security-Policy` restrictions.
The fix
[SECURITY-1793]
core/src/main/java/hudson/model/FileParameterValue.java+8 −0
@@ -40,6 +40,7 @@import java.util.regex.Pattern;import javax.servlet.ServletException;+import jenkins.util.SystemProperties;import org.apache.commons.fileupload.FileItem;import org.apache.commons.fileupload.FileItemHeaders;import org.apache.commons.fileupload.disk.DiskFileItem;@@ -250,6 +251,13 @@ public void doDynamic(StaplerRequest request, StaplerResponse response) throws Sif (request.hasParameter("view")) {response.serveFile(request, data, lastModified, contentLength, "plain.txt");} else {+String csp = SystemProperties.getString(DirectoryBrowserSupport.class.getName() + ".CSP", DirectoryBrowserSupport.DEFAULT_CSP_VALUE);+if (!csp.trim().equals("")) {+// allow users to prevent sending this header by setting empty system property+for (String header : new String[]{"Content-Security-Policy", "X-WebKit-CSP", "X-Content-Security-Policy"}) {+response.setHeader(header, csp);+}+}response.serveFile(request, data, lastModified, contentLength, originalFileName);}} catch (InvalidPathException e) {
test/src/test/java/hudson/model/FileParameterValueSecurity1793Test.java+47 −0
@@ -0,0 +1,47 @@+package hudson.model;++import com.gargoylesoftware.htmlunit.html.HtmlPage;+import org.junit.Ignore;+import org.junit.Rule;+import org.junit.Test;+import org.jvnet.hudson.test.Issue;+import org.jvnet.hudson.test.JenkinsRule;+import org.jvnet.hudson.test.recipes.LocalData;++import static org.junit.Assert.assertEquals;+import static org.junit.Assert.assertFalse;++public class FileParameterValueSecurity1793Test {++@Rule+public JenkinsRule j = new JenkinsRule();++@Issue("SECURITY-1793")+@Test+@LocalData+public void contentSecurityPolicy() throws Exception {+FreeStyleProject p = j.jenkins.getItemByFullName("SECURITY-1793", FreeStyleProject.class);++HtmlPage page = j.createWebClient().goTo("job/" + p.getName() + "/lastSuccessfulBuild/parameters/parameter/html.html/html.html");+for (String header : new String[]{"Content-Security-Policy", "X-WebKit-CSP", "X-Content-Security-Policy"}) {+assertEquals("Header set: " + header, DirectoryBrowserSupport.DEFAULT_CSP_VALUE, page.getWebResponse().getResponseHeaderValue(header));+}++String propName = DirectoryBrowserSupport.class.getName() + ".CSP";+String initialValue = System.getProperty(propName);+try {+System.setProperty(propName, "");+page = j.createWebClient().goTo("job/" + p.getName() + "/lastSuccessfulBuild/parameters/parameter/html.html/html.html");+for (String header : new String[]{"Content-Security-Policy", "X-WebKit-CSP", "X-Content-Security-Policy"}) {+assertFalse("Header not set: " + header, page.getWebResponse().getResponseHeaders().contains(header));+}+} finally {+if (initialValue == null) {+System.clearProperty(DirectoryBrowserSupport.class.getName() + ".CSP");+} else {+System.setProperty(DirectoryBrowserSupport.class.getName() + ".CSP", initialValue);+}+}+}++}
test/src/test/resources/hudson/model/FileParameterValueSecurity1793Test/contentSecurityPolicy/jobs/SECURITY-1793/builds/4/build.xml+41 −0
@@ -0,0 +1,41 @@+<?xml version='1.1' encoding='UTF-8'?>+<build>+<actions>+<hudson.model.ParametersAction>+<safeParameters class="sorted-set"/>+<parameters>+<hudson.model.FileParameterValue>+<name>html.html</name>+<description></description>+<originalFileName>html.html</originalFileName>+<location>html.html</location>+</hudson.model.FileParameterValue>+</parameters>+<parameterDefinitionNames>+<string>html.html</string>+</parameterDefinitionNames>+</hudson.model.ParametersAction>+<hudson.model.CauseAction>+<causeBag class="linked-hash-map">+<entry>+<hudson.model.Cause_-UserIdCause>+<userId>admin</userId>+</hudson.model.Cause_-UserIdCause>+<int>1</int>+</entry>+</causeBag>+</hudson.model.CauseAction>+</actions>+<queueId>28</queueId>+<timestamp>1582828801817</timestamp>+<startTime>1582828801820</startTime>+<result>SUCCESS</result>+<duration>34</duration>+<charset>US-ASCII</charset>+<keepLog>false</keepLog>+<builtOn></builtOn>+<workspace>/.../SECURITY-1793</workspace>+<hudsonVersion>2.164.4-SNAPSHOT</hudsonVersion>+<scm class="hudson.scm.NullChangeLogParser"/>+<culprits class="com.google.common.collect.EmptyImmutableSortedSet"/>+</build>
test/src/test/resources/hudson/model/FileParameterValueSecurity1793Test/contentSecurityPolicy/jobs/SECURITY-1793/builds/4/changelog.xml+1 −0
@@ -0,0 +1 @@+<log/>
test/src/test/resources/hudson/model/FileParameterValueSecurity1793Test/contentSecurityPolicy/jobs/SECURITY-1793/builds/4/fileParameters/html.html+7 −0
@@ -0,0 +1,7 @@+<html>+<body>+<script>+alert(1);+</script>+</body>+</html>
test/src/test/resources/hudson/model/FileParameterValueSecurity1793Test/contentSecurityPolicy/jobs/SECURITY-1793/builds/4/log+3 −0
@@ -0,0 +1,3 @@+Building in workspace /.../SECURITY-1793+Copying file to html.html+Finished: SUCCESS
test/src/test/resources/hudson/model/FileParameterValueSecurity1793Test/contentSecurityPolicy/jobs/SECURITY-1793/config.xml+25 −0
@@ -0,0 +1,25 @@+<?xml version='1.1' encoding='UTF-8'?>+<project>+<description></description>+<keepDependencies>false</keepDependencies>+<properties>+<hudson.model.ParametersDefinitionProperty>+<parameterDefinitions>+<hudson.model.FileParameterDefinition>+<name>html.html</name>+<description></description>+</hudson.model.FileParameterDefinition>+</parameterDefinitions>+</hudson.model.ParametersDefinitionProperty>+</properties>+<scm class="hudson.scm.NullSCM"/>+<canRoam>true</canRoam>+<disabled>false</disabled>+<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>+<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>+<triggers/>+<concurrentBuild>false</concurrentBuild>+<builders/>+<publishers/>+<buildWrappers/>+</project>
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2020-2162
- WEBhttps://github.com/jenkinsci/jenkins/commit/c2d22b241eba718c62996e2ceeb5f2e0e9787f81
- PACKAGEhttps://github.com/jenkinsci/jenkins
- WEBhttps://jenkins.io/security/advisory/2020-03-25/#SECURITY-1793
- WEBhttp://www.openwall.com/lists/oss-security/2020/03/25/2