Skip to content

Commit eace9c4

Browse files
authored
Issue #400 - Check for arbitrary file overwrite vulnerability (zip slip) (#401)
1 parent 9dad59d commit eace9c4

File tree

3 files changed

+80
-3
lines changed

3 files changed

+80
-3
lines changed

core/src/main/java/oracle/weblogic/deploy/util/FileUtils.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,13 +652,22 @@ public static void extractZipFileContent(WLSDeployArchive archiveFile, String zi
652652
Files.createDirectory(Paths.get(extractPath));
653653
}
654654

655+
// verify that each target file is under the extract directory,
656+
// to protect from the file overwrite security vulnerability (zip slip).
657+
String canonicalExtractPath = extractDir.getCanonicalPath();
658+
655659
byte[] buffer = new byte[1024];
656660
FileInputStream fis = new FileInputStream(walletZip);
657661
ZipInputStream zis = new ZipInputStream(fis);
658662
ZipEntry ze = zis.getNextEntry();
659663
while (ze != null) {
660664
String fileName = ze.getName();
661665
File newFile = new File(extractPath + File.separator + fileName);
666+
String canonicalNewFile = newFile.getCanonicalPath();
667+
if(!canonicalNewFile.startsWith(canonicalExtractPath + File.separator)) {
668+
throw new WLSDeployArchiveIOException("WLSDPLY-01119", ze.getName());
669+
}
670+
662671
new File(newFile.getParent()).mkdirs();
663672
FileOutputStream fos = new FileOutputStream(newFile);
664673
int len = zis.read(buffer);
@@ -677,7 +686,8 @@ public static void extractZipFileContent(WLSDeployArchive archiveFile, String zi
677686
Files.delete(Paths.get(walletZip));
678687
}
679688
} catch (IOException | WLSDeployArchiveIOException ioe) {
680-
String message = ExceptionHelper.getMessage("WLSDPLY-01118", METHOD, CLASS, ioe.getLocalizedMessage());
689+
String message = ExceptionHelper.getMessage("WLSDPLY-01118", archiveFile.getArchiveFileName(),
690+
ioe.getLocalizedMessage());
681691
IllegalArgumentException iae = new IllegalArgumentException(message);
682692
LOGGER.throwing(CLASS, METHOD, iae);
683693
throw iae;

core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ WLSDPLY-01114=Deleting the file {1} in directory {0}
128128
WLSDPLY-01115=Unable to delete file {0} from directory {1}
129129
WLSDPLY-01116=Unable to successfully delete the directory {0}
130130
WLSDPLY-01117=Model directory {0} has more than one {1} file, found {2} after previously finding {3}
131-
WLSDPLY-01118=Error extracting zipentry zip file {0}
131+
WLSDPLY-01118=Error extracting zipentry zip file {0}: {1}
132+
WLSDPLY-01119=Zip entry is outside of the target directory: {0}
133+
132134
# oracle.weblogic.deploy.util.ProcessHandler.java
133135
WLSDPLY-01200=Process for command {0} isRunning() unable to get an exit value: {1}
134136
WLSDPLY-01201=ProcessHandler had no registered wait handler when asked to exec() command: {0}

core/src/test/java/oracle/weblogic/deploy/util/FileUtilsTest.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
/*
2-
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
33
* The Universal Permissive License (UPL), Version 1.0
44
*/
55
package oracle.weblogic.deploy.util;
66

7+
import java.io.ByteArrayOutputStream;
78
import java.io.File;
9+
import java.io.FileOutputStream;
810
import java.text.MessageFormat;
11+
import java.util.zip.ZipEntry;
12+
import java.util.zip.ZipOutputStream;
913

1014
import org.junit.Assert;
15+
import org.junit.Before;
1116
import org.junit.Test;
1217

1318
public class FileUtilsTest {
@@ -49,6 +54,16 @@ public class FileUtilsTest {
4954
private static final String APP_PATH = "wlsdeploy/applications/simpleear.ear";
5055
private static final String APP_FILE_NAME = "src/test/resources/simpleear.ear";
5156

57+
private static final File UNIT_TEST_TARGET_DIR = new File(WLSDeployZipFileTest.UNIT_TEST_TARGET_DIR, "fileutils");
58+
private static final String WALLET_PATH = "wlsdeploy/wallet.zip";
59+
60+
@Before
61+
public void initialize() throws Exception {
62+
if(!UNIT_TEST_TARGET_DIR.exists() && !UNIT_TEST_TARGET_DIR.mkdirs()) {
63+
throw new Exception("Unable to create unit test directory: " + UNIT_TEST_TARGET_DIR);
64+
}
65+
}
66+
5267
@Test
5368
public void testNormalFile_parseFileName() throws Exception {
5469
File f = new File(FILE1);
@@ -104,6 +119,56 @@ public void testHashing() throws Exception {
104119
Assert.assertEquals(appHash, archiveHash);
105120
}
106121

122+
@Test
123+
/* A wallet zip inside the archive must not contain an entry such as ../info.txt,
124+
since this creates a file overwrite security vulnerability (zip slip).
125+
*/
126+
public void testZipVulnerability() throws Exception {
127+
String extractPath = UNIT_TEST_TARGET_DIR.getPath();
128+
129+
// an entry with a simple name or path works fine
130+
File zipFile = buildWalletArchiveZip("info.txt");
131+
WLSDeployArchive deployArchive = new WLSDeployArchive(zipFile.getPath());
132+
FileUtils.extractZipFileContent(deployArchive, WALLET_PATH, extractPath);
133+
134+
// an entry with parent directory notation should throw an exception
135+
try {
136+
zipFile = buildWalletArchiveZip("../info.txt");
137+
deployArchive = new WLSDeployArchive(zipFile.getPath());
138+
FileUtils.extractZipFileContent(deployArchive, WALLET_PATH, extractPath);
139+
Assert.fail("Exception not thrown for zip entry outside extract directory");
140+
141+
} catch(IllegalArgumentException e) {
142+
// expected behavior
143+
}
144+
}
145+
146+
/* Build an archive zip containing a wallet zip.
147+
The wallet contains a single entry with the name of the contentName argument.
148+
*/
149+
private File buildWalletArchiveZip(String contentName) throws Exception {
150+
151+
// create the wallet zip content
152+
ByteArrayOutputStream walletBytes = new ByteArrayOutputStream();
153+
try (ZipOutputStream zipStream = new ZipOutputStream(walletBytes)) {
154+
ZipEntry zipEntry = new ZipEntry(contentName);
155+
zipStream.putNextEntry(zipEntry);
156+
byte[] data = "info".getBytes();
157+
zipStream.write(data, 0, data.length);
158+
zipStream.closeEntry();
159+
}
160+
161+
File archiveFile = new File(UNIT_TEST_TARGET_DIR, "archive.zip");
162+
163+
try (ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(archiveFile))) {
164+
ZipEntry zipEntry = new ZipEntry(WALLET_PATH);
165+
zipStream.putNextEntry(zipEntry);
166+
zipStream.write(walletBytes.toByteArray());
167+
zipStream.closeEntry();
168+
}
169+
170+
return archiveFile;
171+
}
107172

108173
private void assertMatch(String name, String got, String expected) {
109174
Assert.assertTrue(MessageFormat.format(FILE_ERR_FORMAT, name, got, expected),

0 commit comments

Comments
 (0)