Skip to content

Commit 6caf226

Browse files
Copilotslachiewicz
andcommitted
Fix Zip Slip vulnerability in JarUtil and add tests
Co-authored-by: slachiewicz <[email protected]>
1 parent 9dae436 commit 6caf226

File tree

2 files changed

+148
-1
lines changed
  • plexus-compilers/plexus-compiler-csharp/src

2 files changed

+148
-1
lines changed

plexus-compilers/plexus-compiler-csharp/src/main/java/org/codehaus/plexus/compiler/csharp/JarUtil.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ public static void extract(Path destDir, File jarFile) throws IOException {
1717
Enumeration<JarEntry> enumEntries = jar.entries();
1818
while (enumEntries.hasMoreElements()) {
1919
JarEntry file = enumEntries.nextElement();
20-
Path f = destDir.resolve(file.getName());
20+
Path f = destDir.resolve(file.getName()).normalize();
2121
if (!f.startsWith(toPath)) {
2222
throw new IOException("Bad zip entry");
2323
}
2424
if (file.isDirectory()) {
2525
Files.createDirectories(f);
2626
continue;
2727
}
28+
Path parent = f.getParent();
29+
if (parent != null) {
30+
Files.createDirectories(parent);
31+
}
2832
try (InputStream is = jar.getInputStream(file);
2933
OutputStream fos = Files.newOutputStream(f)) {
3034
while (is.available() > 0) {
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package org.codehaus.plexus.compiler.csharp;
2+
3+
/**
4+
* The MIT License
5+
*
6+
* Copyright (c) 2005, The Codehaus
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
9+
* this software and associated documentation files (the "Software"), to deal in
10+
* the Software without restriction, including without limitation the rights to
11+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12+
* of the Software, and to permit persons to whom the Software is furnished to do
13+
* so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
import java.io.File;
27+
import java.io.FileOutputStream;
28+
import java.io.IOException;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
31+
import java.util.jar.JarEntry;
32+
import java.util.jar.JarOutputStream;
33+
34+
import org.junit.jupiter.api.Test;
35+
import org.junit.jupiter.api.io.TempDir;
36+
37+
import static org.hamcrest.MatcherAssert.assertThat;
38+
import static org.hamcrest.Matchers.is;
39+
import static org.junit.jupiter.api.Assertions.assertThrows;
40+
41+
/**
42+
* Tests for JarUtil to verify protection against Zip Slip vulnerability.
43+
*/
44+
public class JarUtilTest {
45+
46+
@Test
47+
public void testZipSlipProtection(@TempDir Path tempDir) throws Exception {
48+
// Create a malicious JAR with a path traversal entry
49+
File jarFile = tempDir.resolve("malicious.jar").toFile();
50+
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) {
51+
// Try to escape the extraction directory using path traversal
52+
JarEntry entry = new JarEntry("../../evil.txt");
53+
jos.putNextEntry(entry);
54+
jos.write("malicious content".getBytes());
55+
jos.closeEntry();
56+
}
57+
58+
// Create extraction directory
59+
Path extractDir = tempDir.resolve("extract");
60+
Files.createDirectories(extractDir);
61+
62+
// Attempt to extract - should throw IOException due to path traversal attempt
63+
IOException exception = assertThrows(IOException.class, () -> {
64+
JarUtil.extract(extractDir, jarFile);
65+
});
66+
67+
assertThat(exception.getMessage(), is("Bad zip entry"));
68+
69+
// Verify that the file was not created outside the extraction directory
70+
Path evilFile = tempDir.resolve("evil.txt");
71+
assertThat("Evil file should not exist", Files.exists(evilFile), is(false));
72+
}
73+
74+
@Test
75+
public void testNormalExtraction(@TempDir Path tempDir) throws Exception {
76+
// Create a normal JAR with valid entries
77+
File jarFile = tempDir.resolve("normal.jar").toFile();
78+
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) {
79+
// Add a normal file entry
80+
JarEntry entry = new JarEntry("file.txt");
81+
jos.putNextEntry(entry);
82+
jos.write("normal content".getBytes());
83+
jos.closeEntry();
84+
85+
// Add a file in a subdirectory
86+
JarEntry dirEntry = new JarEntry("subdir/");
87+
jos.putNextEntry(dirEntry);
88+
jos.closeEntry();
89+
90+
JarEntry subFileEntry = new JarEntry("subdir/subfile.txt");
91+
jos.putNextEntry(subFileEntry);
92+
jos.write("subdirectory content".getBytes());
93+
jos.closeEntry();
94+
}
95+
96+
// Create extraction directory
97+
Path extractDir = tempDir.resolve("extract");
98+
Files.createDirectories(extractDir);
99+
100+
// Extract the JAR - should succeed without exception
101+
JarUtil.extract(extractDir, jarFile);
102+
103+
// Verify files were created correctly
104+
Path extractedFile = extractDir.resolve("file.txt");
105+
assertThat("File should exist", Files.exists(extractedFile), is(true));
106+
assertThat("File content should match", new String(Files.readAllBytes(extractedFile)), is("normal content"));
107+
108+
Path extractedSubFile = extractDir.resolve("subdir/subfile.txt");
109+
assertThat("Subdir file should exist", Files.exists(extractedSubFile), is(true));
110+
assertThat(
111+
"Subdir file content should match",
112+
new String(Files.readAllBytes(extractedSubFile)),
113+
is("subdirectory content"));
114+
}
115+
116+
@Test
117+
public void testZipSlipWithComplexPath(@TempDir Path tempDir) throws Exception {
118+
// Create a malicious JAR with a more complex path traversal
119+
File jarFile = tempDir.resolve("complex-malicious.jar").toFile();
120+
try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) {
121+
// Use a path like "subdir/../../evil.txt"
122+
JarEntry entry = new JarEntry("subdir/../../evil.txt");
123+
jos.putNextEntry(entry);
124+
jos.write("malicious content".getBytes());
125+
jos.closeEntry();
126+
}
127+
128+
// Create extraction directory
129+
Path extractDir = tempDir.resolve("extract");
130+
Files.createDirectories(extractDir);
131+
132+
// Attempt to extract - should throw IOException due to path traversal attempt
133+
IOException exception = assertThrows(IOException.class, () -> {
134+
JarUtil.extract(extractDir, jarFile);
135+
});
136+
137+
assertThat(exception.getMessage(), is("Bad zip entry"));
138+
139+
// Verify that the file was not created outside the extraction directory
140+
Path evilFile = tempDir.resolve("evil.txt");
141+
assertThat("Evil file should not exist", Files.exists(evilFile), is(false));
142+
}
143+
}

0 commit comments

Comments
 (0)