Skip to content

Commit

Permalink
[SECURITY-1424]
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffret-b committed Jul 2, 2019
1 parent 279d810 commit 18fc7c0
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 3 deletions.
4 changes: 3 additions & 1 deletion core/src/main/java/hudson/model/FileParameterValue.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -162,7 +164,7 @@ public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener l
if (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 fail
return null;
Expand Down
117 changes: 115 additions & 2 deletions test/src/test/java/hudson/model/FileParameterValueTest.java
Expand Up @@ -94,7 +94,64 @@ public void fileParameter_cannotCreateFile_outsideOfBuildFolder() throws Excepti
// overlong utf-8 encoding
checkUrlNot200AndNotContains(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)));
Expand All @@ -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

Expand Down Expand Up @@ -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"));
}
}

0 comments on commit 18fc7c0

Please sign in to comment.