ExpandTest.java

package org.codehaus.plexus.util;

/*
 * Copyright The Codehaus Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.nio.file.Files;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Test for {@link Expand}.
 */
class ExpandTest extends FileBasedTestCase {

    @Test
    void testZipSlipVulnerabilityWithParentDirectory() throws Exception {
        File tempDir = getTestDirectory();
        File zipFile = new File(tempDir, "malicious.zip");
        File targetDir = new File(tempDir, "extract");
        targetDir.mkdirs();

        // Create a malicious zip with path traversal
        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()))) {
            ZipEntry entry = new ZipEntry("../../evil.txt");
            zos.putNextEntry(entry);
            zos.write("malicious content".getBytes());
            zos.closeEntry();
        }

        Expand expand = new Expand();
        expand.setSrc(zipFile);
        expand.setDest(targetDir);

        // This should throw an exception, not extract the file
        assertThrows(Exception.class, () -> expand.execute());

        // Verify the file was not created outside the target directory
        File evilFile = new File(tempDir, "evil.txt");
        assertFalse(evilFile.exists(), "File should not be extracted outside target directory");
    }

    @Test
    void testZipSlipVulnerabilityWithAbsolutePath() throws Exception {
        File tempDir = getTestDirectory();
        File zipFile = new File(tempDir, "malicious-absolute.zip");
        File targetDir = new File(tempDir, "extract-abs");
        targetDir.mkdirs();

        // Create a malicious zip with absolute path
        File evilTarget = new File("/tmp/evil-absolute.txt");
        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()))) {
            ZipEntry entry = new ZipEntry(evilTarget.getAbsolutePath());
            zos.putNextEntry(entry);
            zos.write("malicious content".getBytes());
            zos.closeEntry();
        }

        Expand expand = new Expand();
        expand.setSrc(zipFile);
        expand.setDest(targetDir);

        // This should throw an exception, not extract the file
        assertThrows(Exception.class, () -> expand.execute());

        // Verify the file was not created at the absolute path
        assertFalse(evilTarget.exists(), "File should not be extracted to absolute path");
    }

    @Test
    void testZipSlipVulnerabilityWithSimilarDirectoryName() throws Exception {
        File tempDir = getTestDirectory();
        File zipFile = new File(tempDir, "malicious-similar.zip");
        File targetDir = new File(tempDir, "extract");
        targetDir.mkdirs();

        // Create a directory with a similar name to test prefix matching vulnerability
        File similarDir = new File(tempDir, "extract-evil");
        similarDir.mkdirs();

        // Create a malicious zip that tries to exploit prefix matching
        // If targetDir is /tmp/extract, this tries to write to /tmp/extract-evil/file.txt
        String maliciousPath = "../extract-evil/evil.txt";
        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()))) {
            ZipEntry entry = new ZipEntry(maliciousPath);
            zos.putNextEntry(entry);
            zos.write("malicious content".getBytes());
            zos.closeEntry();
        }

        Expand expand = new Expand();
        expand.setSrc(zipFile);
        expand.setDest(targetDir);

        // This should throw an exception, not extract the file
        assertThrows(Exception.class, () -> expand.execute());

        // Verify the file was not created in the similar directory
        File evilFile = new File(similarDir, "evil.txt");
        assertFalse(evilFile.exists(), "File should not be extracted to directory with similar name");
    }

    @Test
    void testNormalZipExtraction() throws Exception {
        File tempDir = getTestDirectory();
        File zipFile = new File(tempDir, "normal.zip");
        File targetDir = new File(tempDir, "extract-normal");
        targetDir.mkdirs();

        // Create a normal zip
        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()))) {
            ZipEntry entry = new ZipEntry("subdir/normal.txt");
            zos.putNextEntry(entry);
            zos.write("normal content".getBytes());
            zos.closeEntry();
        }

        Expand expand = new Expand();
        expand.setSrc(zipFile);
        expand.setDest(targetDir);

        // This should succeed
        expand.execute();

        // Verify the file was created in the correct location
        File normalFile = new File(targetDir, "subdir/normal.txt");
        assertTrue(normalFile.exists(), "File should be extracted to correct location");
    }
}