|
15 | 15 | package com.amazonaws.codepipeline.jenkinsplugin;
|
16 | 16 |
|
17 | 17 | import static org.junit.Assert.assertEquals;
|
| 18 | +import static org.junit.Assert.assertTrue; |
18 | 19 | import static org.junit.Assert.fail;
|
19 | 20 | import static org.mockito.Mockito.when;
|
20 | 21 |
|
|
25 | 26 | import java.io.FileOutputStream;
|
26 | 27 | import java.io.IOException;
|
27 | 28 | import java.io.PrintWriter;
|
| 29 | +import java.nio.file.FileSystems; |
28 | 30 | import java.nio.file.FileVisitResult;
|
29 | 31 | import java.nio.file.Files;
|
30 | 32 | import java.nio.file.Path;
|
|
58 | 60 | @RunWith(ExtractionToolsTest.class)
|
59 | 61 | @Suite.SuiteClasses({
|
60 | 62 | ExtractionToolsTest.GetCompressionTypeTest.class,
|
61 |
| - ExtractionToolsTest.DecompressFileTest.class |
| 63 | + ExtractionToolsTest.DecompressFileTest.class, |
| 64 | + ExtractionToolsTest.ExtractionPathTraversalTest.class |
62 | 65 | })
|
63 | 66 | public class ExtractionToolsTest extends Suite {
|
64 | 67 |
|
@@ -341,4 +344,76 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr
|
341 | 344 | }
|
342 | 345 | }
|
343 | 346 |
|
| 347 | + public static class ExtractionPathTraversalTest extends TestBase { |
| 348 | + private static final String BASE_DIR = "base"; |
| 349 | + private Path testDir; |
| 350 | + |
| 351 | + @Before |
| 352 | + public void setUp() throws IOException { |
| 353 | + testDir = Files.createTempDirectory("ExtractionPathTraversalTest.tmp."); |
| 354 | + } |
| 355 | + |
| 356 | + @After |
| 357 | + public void tearDown() throws IOException { |
| 358 | + FileUtils.deleteDirectory(testDir.toFile()); |
| 359 | + } |
| 360 | + |
| 361 | + private void testPathTraversal(final String filename) throws IOException { |
| 362 | + final String filePath = getClass().getClassLoader().getResource(filename).getFile(); |
| 363 | + final String osAppropriatePath = System.getProperty("os.name").contains("indow") ? filePath.substring(1) : filePath; |
| 364 | + final Path cliCompressedFile = Paths.get(osAppropriatePath); |
| 365 | + final Path baseDir = FileSystems.getDefault().getPath(testDir.toFile().getAbsolutePath(), BASE_DIR); |
| 366 | + Files.createDirectories(baseDir); |
| 367 | + |
| 368 | + // path traversal is dependent on zip file contents and the file system in use, for that reason the test may |
| 369 | + // or may not throw an IOException. But, in either case, no file should be produced on top level testDir. |
| 370 | + try { |
| 371 | + ExtractionTools.decompressFile( |
| 372 | + cliCompressedFile.toFile(), |
| 373 | + baseDir.toFile(), |
| 374 | + CompressionType.Zip, |
| 375 | + null); |
| 376 | + } catch (final IOException e) { |
| 377 | + assertTrue(e.getMessage().startsWith("The compressed input file contains files targeting an invalid destination: ")); |
| 378 | + } |
| 379 | + final String[] files = testDir.toFile().list(); |
| 380 | + assertEquals(filename + " should produce no extra entries in test dir after extraction", 1, files.length); |
| 381 | + assertEquals("the only entry in test dir should be '" + BASE_DIR + "' directory", BASE_DIR, files[0]); |
| 382 | + assertTrue(filename + " should produce a file named good.txt in '" + BASE_DIR + "' directory", |
| 383 | + Files.exists(baseDir.resolve("good.txt"))); |
| 384 | + } |
| 385 | + |
| 386 | + @Test |
| 387 | + public void shouldNotTraverseBaseDirOnExtractionUnix() throws IOException { |
| 388 | + // dir-traversal-unix.zip: |
| 389 | + // - good.txt |
| 390 | + // - ../evil.txt |
| 391 | + testPathTraversal("dir-traversal-unix.zip"); |
| 392 | + } |
| 393 | + |
| 394 | + @Test |
| 395 | + public void shouldNotTraverseBaseDirOnExtractionUnixIfFilenameMatchesBaseDir() throws IOException { |
| 396 | + // dir-traversal-unix2.zip: |
| 397 | + // - good.txt |
| 398 | + // - ../base-evil.txt |
| 399 | + testPathTraversal("dir-traversal-unix2.zip"); |
| 400 | + } |
| 401 | + |
| 402 | + @Test |
| 403 | + public void shouldNotTraverseBaseDirOnExtractionWin() throws IOException { |
| 404 | + // dir-traversal-win.zip: |
| 405 | + // - good.txt |
| 406 | + // - ..\evil.txt |
| 407 | + testPathTraversal("dir-traversal-win.zip"); |
| 408 | + } |
| 409 | + |
| 410 | + @Test |
| 411 | + public void shouldNotTraverseBaseDirOnExtractionWinIfFilenameMatchesBaseDir() throws IOException { |
| 412 | + // dir-traversal-win2.zip: |
| 413 | + // - good.txt |
| 414 | + // - ..\base-evil.txt |
| 415 | + testPathTraversal("dir-traversal-win2.zip"); |
| 416 | + } |
| 417 | + } |
| 418 | + |
344 | 419 | }
|
0 commit comments