Polish "Prevent extracting zip entries outside of destination path"
See gh-25505
This commit is contained in:
parent
2993e68715
commit
c6ca7a53ab
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2020 the original author or authors.
|
* Copyright 2012-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -29,7 +29,6 @@ import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code 'extract'} tools command.
|
* The {@code 'extract'} tools command.
|
||||||
|
@ -86,16 +85,19 @@ class ExtractCommand extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void write(ZipInputStream zip, ZipEntry entry, File destination) throws IOException {
|
private void write(ZipInputStream zip, ZipEntry entry, File destination) throws IOException {
|
||||||
String path = StringUtils.cleanPath(entry.getName());
|
String canonicalOutputPath = destination.getCanonicalPath() + File.separator;
|
||||||
File file = new File(destination, path);
|
File file = new File(destination, entry.getName());
|
||||||
if (file.getCanonicalPath().startsWith(destination.getCanonicalPath() + File.separator)) {
|
String canonicalEntryPath = file.getCanonicalPath();
|
||||||
|
Assert.state(canonicalEntryPath.startsWith(canonicalOutputPath),
|
||||||
|
() -> "Entry '" + entry.getName() + "' would be written to '" + canonicalEntryPath
|
||||||
|
+ "'. This is outside the output location of '" + canonicalOutputPath
|
||||||
|
+ "'. Verify the contents of your archive.");
|
||||||
mkParentDirs(file);
|
mkParentDirs(file);
|
||||||
try (OutputStream out = new FileOutputStream(file)) {
|
try (OutputStream out = new FileOutputStream(file)) {
|
||||||
StreamUtils.copy(zip, out);
|
StreamUtils.copy(zip, out);
|
||||||
}
|
}
|
||||||
Files.setAttribute(file.toPath(), "creationTime", entry.getCreationTime());
|
Files.setAttribute(file.toPath(), "creationTime", entry.getCreationTime());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void mkParentDirs(File file) throws IOException {
|
private void mkParentDirs(File file) throws IOException {
|
||||||
mkDirs(file.getParentFile());
|
mkDirs(file.getParentFile());
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2020 the original author or authors.
|
* Copyright 2012-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,6 +23,7 @@ import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ import static org.mockito.BDDMockito.given;
|
||||||
* Tests for {@link ExtractCommand}.
|
* Tests for {@link ExtractCommand}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
class ExtractCommandTests {
|
class ExtractCommandTests {
|
||||||
|
|
||||||
|
@ -76,6 +78,7 @@ class ExtractCommandTests {
|
||||||
assertThat(new File(this.extract, "b/b/b.jar")).exists();
|
assertThat(new File(this.extract, "b/b/b.jar")).exists();
|
||||||
assertThat(new File(this.extract, "c/c/c.jar")).exists();
|
assertThat(new File(this.extract, "c/c/c.jar")).exists();
|
||||||
assertThat(new File(this.extract, "d")).isDirectory();
|
assertThat(new File(this.extract, "d")).isDirectory();
|
||||||
|
assertThat(new File(this.extract.getParentFile(), "e.jar")).doesNotExist();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -95,6 +98,7 @@ class ExtractCommandTests {
|
||||||
assertThat(this.extract.list()).containsOnly("a", "c");
|
assertThat(this.extract.list()).containsOnly("a", "c");
|
||||||
assertThat(new File(this.extract, "a/a/a.jar")).exists();
|
assertThat(new File(this.extract, "a/a/a.jar")).exists();
|
||||||
assertThat(new File(this.extract, "c/c/c.jar")).exists();
|
assertThat(new File(this.extract, "c/c/c.jar")).exists();
|
||||||
|
assertThat(new File(this.extract.getParentFile(), "e.jar")).doesNotExist();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -110,7 +114,28 @@ class ExtractCommandTests {
|
||||||
.withMessageContaining("not compatible with layertools");
|
.withMessageContaining("not compatible with layertools");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void runWithJarFileThatWouldWriteEntriesOutsideDestinationFails() throws IOException {
|
||||||
|
this.jarFile = createJarFile("test.jar", (out) -> {
|
||||||
|
try {
|
||||||
|
out.putNextEntry(new ZipEntry("e/../../e.jar"));
|
||||||
|
out.closeEntry();
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> this.command.run(Collections.emptyMap(), Collections.emptyList()))
|
||||||
|
.withMessageContaining("Entry 'e/../../e.jar' would be written");
|
||||||
|
}
|
||||||
|
|
||||||
private File createJarFile(String name) throws IOException {
|
private File createJarFile(String name) throws IOException {
|
||||||
|
return createJarFile(name, (out) -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private File createJarFile(String name, Consumer<ZipOutputStream> streamHandler) throws IOException {
|
||||||
File file = new File(this.temp, name);
|
File file = new File(this.temp, name);
|
||||||
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) {
|
try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file))) {
|
||||||
out.putNextEntry(new ZipEntry("a/"));
|
out.putNextEntry(new ZipEntry("a/"));
|
||||||
|
@ -127,6 +152,7 @@ class ExtractCommandTests {
|
||||||
out.closeEntry();
|
out.closeEntry();
|
||||||
out.putNextEntry(new ZipEntry("d/"));
|
out.putNextEntry(new ZipEntry("d/"));
|
||||||
out.closeEntry();
|
out.closeEntry();
|
||||||
|
streamHandler.accept(out);
|
||||||
}
|
}
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue