|
1 | 1 | /* |
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. |
3 | 3 | * The Universal Permissive License (UPL), Version 1.0 |
4 | 4 | */ |
5 | 5 | package oracle.weblogic.deploy.util; |
6 | 6 |
|
| 7 | +import java.io.ByteArrayOutputStream; |
7 | 8 | import java.io.File; |
| 9 | +import java.io.FileOutputStream; |
8 | 10 | import java.text.MessageFormat; |
| 11 | +import java.util.zip.ZipEntry; |
| 12 | +import java.util.zip.ZipOutputStream; |
9 | 13 |
|
10 | 14 | import org.junit.Assert; |
| 15 | +import org.junit.Before; |
11 | 16 | import org.junit.Test; |
12 | 17 |
|
13 | 18 | public class FileUtilsTest { |
@@ -49,6 +54,16 @@ public class FileUtilsTest { |
49 | 54 | private static final String APP_PATH = "wlsdeploy/applications/simpleear.ear"; |
50 | 55 | private static final String APP_FILE_NAME = "src/test/resources/simpleear.ear"; |
51 | 56 |
|
| 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 | + |
52 | 67 | @Test |
53 | 68 | public void testNormalFile_parseFileName() throws Exception { |
54 | 69 | File f = new File(FILE1); |
@@ -104,6 +119,56 @@ public void testHashing() throws Exception { |
104 | 119 | Assert.assertEquals(appHash, archiveHash); |
105 | 120 | } |
106 | 121 |
|
| 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 | + } |
107 | 172 |
|
108 | 173 | private void assertMatch(String name, String got, String expected) { |
109 | 174 | Assert.assertTrue(MessageFormat.format(FILE_ERR_FORMAT, name, got, expected), |
|
0 commit comments