Improper Limitation of a Pathname to a Restricted Directory 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.22.177 → fixed in 2.186
Details
A path traversal vulnerability in Jenkins 2.185 and earlier, LTS 2.176.1 and earlier in `core/src/main/java/hudson/model/FileParameterValue.java` allowed attackers with Job/Configure permission to define a file parameter with a file name outside the intended directory, resulting in an arbitrary file write on the Jenkins master when scheduling a build.
The fix
[SECURITY-1424]
core/src/main/java/hudson/model/FileParameterValue.java+3 −1
@@ -37,6 +37,7 @@import java.nio.file.Files;import java.nio.file.InvalidPathException;import java.nio.file.Path;+import java.util.regex.Pattern;import javax.servlet.ServletException;import org.apache.commons.fileupload.FileItem;@@ -65,6 +66,7 @@*/public class FileParameterValue extends ParameterValue {private static final String FOLDER_NAME = "fileParameters";+private static final Pattern PROHIBITED_DOUBLE_DOT = Pattern.compile(".*[\\\\/]\\.\\.[\\\\/].*");/*** Escape hatch for SECURITY-1074, fileParameter used to escape their expected folder.@@ -162,7 +164,7 @@ public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener lif (ws == null) {throw new IllegalStateException("The workspace should be created when setUp method is called");}-if (!ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE && !ws.isDescendant(location)) {+if (!ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE && (PROHIBITED_DOUBLE_DOT.matcher(location).matches() || !ws.isDescendant(location))) {listener.error("Rejecting file path escaping base directory with relative path: " + location);// force the build to failreturn null;
test/src/test/java/hudson/model/FileParameterValueTest.java+115 −2
@@ -94,7 +94,64 @@ public void fileParameter_cannotCreateFile_outsideOfBuildFolder() throws Excepti// overlong utf-8 encodingcheckUrlNot200AndNotContains(wc, build.getUrl() + "parameters/parameter/%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%af%c0%2e%c0%2e%c0%afroot-level.txt/uploaded-file.txt", uploadedContent);}-++@Test+@Issue("SECURITY-1424")+public void fileParameter_cannotCreateFile_outsideOfBuildFolder_SEC1424() throws Exception {+// you can test the behavior before the correction by setting FileParameterValue.ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE to true++FilePath root = j.jenkins.getRootPath();++FreeStyleProject p = j.createFreeStyleProject();+p.addProperty(new ParametersDefinitionProperty(Collections.singletonList(+new FileParameterDefinition("dir/../../../pwned", null)+)));++assertThat(root.child("pwned").exists(), equalTo(false));++String uploadedContent = "test-content";+File uploadedFile = tmp.newFile();+FileUtils.write(uploadedFile, uploadedContent);++FreeStyleBuild build = p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(+new FileParameterValue("dir/../../../pwned", uploadedFile, "uploaded-file.txt")+)).get();++assertThat(build.getResult(), equalTo(Result.FAILURE));+assertThat(root.child("pwned").exists(), equalTo(false));++// ensure also the file is not reachable by request+JenkinsRule.WebClient wc = j.createWebClient();+wc.getOptions().setThrowExceptionOnFailingStatusCode(false);+}++@Test+public void fileParameter_cannotCreateFile_outsideOfBuildFolder_LeadingDoubleDot() throws Exception {+FilePath root = j.jenkins.getRootPath();++FreeStyleProject p = j.createFreeStyleProject();+p.addProperty(new ParametersDefinitionProperty(Collections.singletonList(+new FileParameterDefinition("../pwned", null)+)));++assertThat(root.child("pwned").exists(), equalTo(false));++String uploadedContent = "test-content";+File uploadedFile = tmp.newFile();+FileUtils.write(uploadedFile, uploadedContent);++FreeStyleBuild build = p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(+new FileParameterValue("../pwned", uploadedFile, "uploaded-file.txt")+)).get();++assertThat(build.getResult(), equalTo(Result.FAILURE));+assertThat(root.child("pwned").exists(), equalTo(false));++// ensure also the file is not reachable by request+JenkinsRule.WebClient wc = j.createWebClient();+wc.getOptions().setThrowExceptionOnFailingStatusCode(false);+}+private void checkUrlNot200AndNotContains(JenkinsRule.WebClient wc, String url, String contentNotPresent) throws Exception {Page pageForEncoded = wc.goTo(url, null);assertThat(pageForEncoded.getWebResponse().getStatusCode(), not(equalTo(200)));@@ -104,7 +161,7 @@ private void checkUrlNot200AndNotContains(JenkinsRule.WebClient wc, String url,@Test@Issue("SECURITY-1074")public void fileParameter_cannotCreateFile_outsideOfBuildFolder_backslashEdition() throws Exception {-Assume.assumeTrue("Backslash are only dangerous on Windows", Functions.isWindows());+Assume.assumeTrue("Backslashes are only dangerous on Windows", Functions.isWindows());// you can test the behavior before the correction by setting FileParameterValue.ALLOW_FOLDER_TRAVERSAL_OUTSIDE_WORKSPACE to true@@ -267,4 +324,60 @@ public void fileParameter_canStillUse_internalHierarchy() throws Exception {String workspaceParentContent = workspaceParentPage.getWebResponse().getContentAsString();assertThat(workspaceParentContent, containsString("child2.txt"));}++@Test+public void fileParameter_canStillUse_doubleDotsInFileName() throws Exception {+FreeStyleProject p = j.createFreeStyleProject();+p.addProperty(new ParametersDefinitionProperty(Arrays.asList(+new FileParameterDefinition("weird..name.txt", null)+)));++File uploadedFile = tmp.newFile();+FileUtils.write(uploadedFile, "test1");++FreeStyleBuild build = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(+new FileParameterValue("weird..name.txt", uploadedFile, "uploaded-file.txt")+)));++// files are correctly saved in the build "fileParameters" folder+File directChild = new File(build.getRootDir(), "fileParameters/weird..name.txt");+assertTrue(directChild.exists());++// both are correctly copied inside the workspace+assertTrue(build.getWorkspace().child("weird..name.txt").exists());++// and reachable using request+JenkinsRule.WebClient wc = j.createWebClient();+HtmlPage workspacePage = wc.goTo(p.getUrl() + "ws");+String workspaceContent = workspacePage.getWebResponse().getContentAsString();+assertThat(workspaceContent, containsString("weird..name.txt"));+}++@Test+public void fileParameter_canStillUse_TildeInFileName() throws Exception {+FreeStyleProject p = j.createFreeStyleProject();+p.addProperty(new ParametersDefinitionProperty(Arrays.asList(+new FileParameterDefinition("~name", null)+)));++File uploadedFile = tmp.newFile();+FileUtils.write(uploadedFile, "test1");++FreeStyleBuild build = j.assertBuildStatusSuccess(p.scheduleBuild2(0, new Cause.UserIdCause(), new ParametersAction(+new FileParameterValue("~name", uploadedFile, "uploaded-file.txt")+)));++// files are correctly saved in the build "fileParameters" folder+File directChild = new File(build.getRootDir(), "fileParameters/~name");+assertTrue(directChild.exists());++// both are correctly copied inside the workspace+assertTrue(build.getWorkspace().child("~name").exists());++// and reachable using request+JenkinsRule.WebClient wc = j.createWebClient();+HtmlPage workspacePage = wc.goTo(p.getUrl() + "ws");+String workspaceContent = workspacePage.getWebResponse().getContentAsString();+assertThat(workspaceContent, containsString("~name"));+}}
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2019-10352
- WEBhttps://github.com/jenkinsci/jenkins/commit/18fc7c0b466553cbd4f790db3270964305bee7f9
- WEBhttps://access.redhat.com/errata/RHSA-2019:2503
- WEBhttps://access.redhat.com/errata/RHSA-2019:2548
- PACKAGEhttps://github.com/jenkinsci/jenkins
- WEBhttps://jenkins.io/security/advisory/2019-07-17/#SECURITY-1424
- WEBhttps://web.archive.org/web/20200227033157/http://www.securityfocus.com/bid/109299
- WEBhttps://www.tenable.com/security/research/tra-2019-35
- WEBhttp://www.openwall.com/lists/oss-security/2019/07/17/2