Skip to content

Commit 08b4064

Browse files
authored
Solve Zip Slip Path Traversal in quarkus-openapi-generator ApicurioCodegenWrapper class (#1524)
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent e062c8c commit 08b4064

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

server/deployment/src/main/java/io/quarkiverse/openapi/server/generator/deployment/codegen/apicurio/ApicurioCodegenWrapper.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,17 @@ public void generate(Path openApiResource) throws CodeGenException {
9393
}
9494

9595
private void unzip(File fromZipFile, File toOutputDir) throws IOException, CodeGenException {
96+
Path outputDirPath = toOutputDir.toPath().toAbsolutePath().normalize();
9697
try (java.util.zip.ZipFile zipFile = new ZipFile(fromZipFile)) {
9798
Enumeration<? extends ZipEntry> entries = zipFile.entries();
9899
while (entries.hasMoreElements()) {
99100
ZipEntry entry = entries.nextElement();
101+
Path entryPath = outputDirPath.resolve(entry.getName()).toAbsolutePath().normalize();
102+
if (!entryPath.startsWith(outputDirPath)) {
103+
throw new IOException("Invalid ZIP entry: " + entry.getName());
104+
}
105+
File entryDestination = entryPath.toFile();
100106
assertGenerationSucceeded(zipFile, entry);
101-
File entryDestination = new File(toOutputDir, entry.getName());
102107
if (entry.isDirectory()) {
103108
entryDestination.mkdirs();
104109
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.quarkiverse.openapi.server.generator.deployment.codegen.apicurio;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.lang.reflect.InvocationTargetException;
10+
import java.lang.reflect.Method;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.zip.ZipEntry;
14+
import java.util.zip.ZipOutputStream;
15+
16+
import org.eclipse.microprofile.config.Config;
17+
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.io.TempDir;
20+
21+
class ApicurioCodegenWrapperTest {
22+
23+
@TempDir
24+
Path tempDir;
25+
26+
@Test
27+
void shouldExtractRegularEntryInsideOutputDir() throws Exception {
28+
Path zipPath = tempDir.resolve("safe.zip");
29+
createZip(zipPath, "nested/proof.txt", "ok");
30+
31+
Path outputDir = tempDir.resolve("out");
32+
Files.createDirectories(outputDir);
33+
34+
invokeUnzip(zipPath.toFile(), outputDir.toFile());
35+
36+
Path extractedFile = outputDir.resolve("nested/proof.txt");
37+
assertTrue(Files.exists(extractedFile));
38+
assertEquals("ok", Files.readString(extractedFile));
39+
}
40+
41+
@Test
42+
void shouldRejectPathTraversalEntry() throws Exception {
43+
Path zipPath = tempDir.resolve("traversal.zip");
44+
createZip(zipPath, "../../proof.txt", "pwned");
45+
46+
Path outputDir = tempDir.resolve("out");
47+
Files.createDirectories(outputDir);
48+
49+
IOException error = assertThrows(IOException.class, () -> invokeUnzip(zipPath.toFile(), outputDir.toFile()));
50+
assertTrue(error.getMessage().contains("Invalid ZIP entry"));
51+
}
52+
53+
private static void createZip(Path zipPath, String entryName, String content) throws IOException {
54+
try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(zipPath))) {
55+
zipOutputStream.putNextEntry(new ZipEntry(entryName));
56+
zipOutputStream.write(content.getBytes());
57+
zipOutputStream.closeEntry();
58+
}
59+
}
60+
61+
private static void invokeUnzip(File zipFile, File outputDir) throws Exception {
62+
Method unzipMethod = ApicurioCodegenWrapper.class.getDeclaredMethod("unzip", File.class, File.class);
63+
unzipMethod.setAccessible(true);
64+
65+
Config config = ConfigProviderResolver.instance().getBuilder().build();
66+
ApicurioCodegenWrapper wrapper = new ApicurioCodegenWrapper(config, outputDir);
67+
68+
try {
69+
unzipMethod.invoke(wrapper, zipFile, outputDir);
70+
} catch (InvocationTargetException e) {
71+
Throwable cause = e.getCause();
72+
if (cause instanceof IOException ioException) {
73+
throw ioException;
74+
}
75+
if (cause instanceof RuntimeException runtimeException) {
76+
throw runtimeException;
77+
}
78+
throw new RuntimeException(cause);
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)