Skip to content

Commit fc8b9a1

Browse files
authored
Normalize the destination directory when extracting zip files (#1125)
Otherwise, relative installDirectory paths cannot be used because the zip-slip check fails with "Bad zip entry" exception even the zip is fine. Closes #1124
1 parent 4045aaf commit fc8b9a1

File tree

5 files changed

+56
-8
lines changed

5 files changed

+56
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Last public release: [![Maven Central](https://maven-badges.herokuapp.com/maven-
44

55
## Changelog
66

7+
### 1.14.3
8+
9+
* Prevent `Bad zip entry` exceptions when installing Node to a relative directory ([#1124](https://github.com/eirslett/frontend-maven-plugin/issues/1124))
10+
711
### 1.14.2
812

913
* Prevent corrupt downloaded files by waiting for the download to complete before writing the file to disk.

frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/ArchiveExtractor.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
55
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
66

7+
import java.io.BufferedOutputStream;
78
import java.io.File;
89
import java.io.FileInputStream;
910
import java.io.FileOutputStream;
1011
import java.io.IOException;
1112
import java.io.InputStream;
1213
import java.io.OutputStream;
1314
import java.nio.file.AccessDeniedException;
15+
import java.nio.file.Files;
16+
import java.nio.file.Path;
17+
import java.nio.file.Paths;
1418
import java.util.Enumeration;
1519
import java.util.zip.ZipEntry;
1620
import java.util.zip.ZipFile;
@@ -73,31 +77,29 @@ public void extract(String archive, String destinationDirectory) throws ArchiveE
7377
"Unexpected interruption of while waiting for extraction process", e);
7478
}
7579
} else if ("zip".equals(FileUtils.getExtension(archiveFile.getAbsolutePath()))) {
76-
ZipFile zipFile = new ZipFile(archiveFile);
77-
try {
80+
Path destinationPath = Paths.get(destinationDirectory).normalize();
81+
try (ZipFile zipFile = new ZipFile(archiveFile)) {
7882
Enumeration<? extends ZipEntry> entries = zipFile.entries();
7983
while (entries.hasMoreElements()) {
8084
ZipEntry entry = entries.nextElement();
81-
final File destPath = new File(destinationDirectory, entry.getName());
82-
if (!destPath.toPath().normalize().startsWith(destinationDirectory)) {
85+
final Path destPath = destinationPath.resolve(entry.getName()).normalize();
86+
if (!destPath.startsWith(destinationPath)) {
8387
throw new RuntimeException("Bad zip entry");
8488
}
85-
prepDestination(destPath, entry.isDirectory());
89+
prepDestination(destPath.toFile(), entry.isDirectory());
8690
if (!entry.isDirectory()) {
8791
InputStream in = null;
8892
OutputStream out = null;
8993
try {
9094
in = zipFile.getInputStream(entry);
91-
out = new FileOutputStream(destPath);
95+
out = new BufferedOutputStream(Files.newOutputStream(destPath));
9296
IOUtils.copy(in, out);
9397
} finally {
9498
IOUtils.closeQuietly(in);
9599
IOUtils.closeQuietly(out);
96100
}
97101
}
98102
}
99-
} finally {
100-
zipFile.close();
101103
}
102104
} else {
103105
// TarArchiveInputStream can be constructed with a normal FileInputStream if

frontend-plugin-core/src/test/java/com/github/eirslett/maven/plugins/frontend/lib/DefaultArchiveExtractorTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
public class DefaultArchiveExtractorTest {
1818

1919
private final String BAD_TAR = "src/test/resources/bad.tgz";
20+
private final String BAD_ZIP = "src/test/resources/bad.zip"; // sample zip with zip slip vulnerability
2021
private final String GOOD_TAR = "src/test/resources/good.tgz";
22+
private final String GOOD_ZIP = "src/test/resources/good.zip";
2123

2224
@TempDir
2325
public File temp;
@@ -48,6 +50,26 @@ public void extractBadTarFile() {
4850
extractor.extract(BAD_TAR, temp.getPath()));
4951
}
5052

53+
@Test
54+
public void extractGoodZipFile() throws Exception {
55+
assertGoodZipExtractedTo(temp);
56+
}
57+
58+
@Test
59+
public void extractGoodZipFileWithRelTarget() throws Exception {
60+
assertGoodZipExtractedTo(createRelPath(temp));
61+
}
62+
63+
@Test
64+
public void extractBadZipFile() {
65+
assertBadZipThrowsException(temp);
66+
}
67+
68+
@Test
69+
public void extractBadZipFileWithRelTarget() throws Exception {
70+
assertBadZipThrowsException(createRelPath(temp));
71+
}
72+
5173
@Test
5274
public void extractBadTarFileSymlink() {
5375
File destination = new File(temp + "/destination");
@@ -56,6 +78,26 @@ public void extractBadTarFileSymlink() {
5678
Assertions.assertThrows(ArchiveExtractionException.class, () -> extractor.extract(BAD_TAR, link.toString()));
5779
}
5880

81+
private void assertBadZipThrowsException(File targetDir) {
82+
Assertions.assertThrows(RuntimeException.class, () ->
83+
extractor.extract(BAD_ZIP, targetDir.getPath()));
84+
}
85+
86+
private void assertGoodZipExtractedTo(File targetDir) throws Exception {
87+
extractor.extract(GOOD_ZIP, targetDir.getPath());
88+
String nameOfFileInZip = "zip";
89+
Assertions.assertTrue(new File(temp, nameOfFileInZip).isFile(), "zip content not found in target directory");
90+
}
91+
92+
private static File createRelPath(File orig) throws IOException {
93+
orig = orig.getCanonicalFile();
94+
String dirName = orig.getName();
95+
File result = new File(orig, "../" + dirName);
96+
Assertions.assertNotEquals(orig, result); // ensure result is different from input
97+
Assertions.assertEquals(orig, result.getCanonicalFile()); // ensure result points to same dir
98+
return result;
99+
}
100+
59101
private Path createSymlinkOrSkipTest(Path link, Path target) {
60102
try {
61103
return Files.createSymbolicLink(link, target);
545 Bytes
Binary file not shown.
104 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)