CVE-2023-37460 (Java, published 2023-07-25T20:15Z)
Description: value: Plexis Archiver is a collection of Plexus components to create archives or extract archives to a directory with a unified `Archiver`/`UnArchiver` API. Prior to version 4.8.0, using AbstractUnArchiver for extracting an archive might lead to an arbitrary file creation and possibly remote code execution. When extracting an archive with an entry that already exists in the destination directory as a symbolic link whose target does not exist - the `resolveFile()` function will return the symlink's source instead of its target, which will pass the verification that ensures the file will not be extracted outside of the destination directory. Later `Files.newOutputStream()`, that follows symlinks by default,  will actually write the entry's content to the symlink's target. Whoever uses plexus archiver to extract an untrusted archive is vulnerable to an arbitrary file creation and possibly remote code execution. Version 4.8.0 contains a patch for this issue.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-61 \u2014 UNIX Symbolic Link (Symlink) Following
The product, when opening a file or directory, does not sufficiently account for when the file is a symbolic link that resolves to a target outside of the intended control sphere. This could allow an attacker to cause the product to operate on unauthorized files.
Repository: https://github.com/codehaus-plexus/plexus-archiver
Files changed:
src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java
src/test/java/org/codehaus/plexus/archiver/SymlinkTest.java
src/test/java/org/codehaus/plexus/archiver/zip/ZipArchiverTest.java
src/test/resources/symlinks/regen.sh
Commit message: Avoid override target symlink by standard file in AbstractUnArchiver


Vulnerable code:
import java.io.OutputStream;
import org.codehaus.plexus.util.IOUtil;
                try (OutputStream out = Files.newOutputStream(targetFileName.toPath())) {
                    IOUtil.copy(compressedInputStream, out);
                }

Fixed code:
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

        // don't allow override target symlink by standard file
        if (StringUtils.isEmpty(symlinkDestination) && Files.isSymbolicLink(canonicalDestPath)) {
            throw new ArchiverException("Entry is outside of the target directory (" + entryName + ")");
        }

                Files.copy(compressedInputStream, targetFileName.toPath(), REPLACE_EXISTING);
        // second unpacking should also work
        unarchiver.extract();
        // second unpacking should also work
        unarchiver.extract();
import static org.junit.jupiter.api.Assertions.assertThrows;
    @Test
    @DisabledOnOs(OS.WINDOWS)
    void testNonExistingSymlink() throws Exception {
        File zipFile = new File("src/test/resources/symlinks/non_existing_symlink.zip");
        ZipUnArchiver unArchiver = getZipUnArchiver(zipFile);
        String tmpdir = Files.createTempDirectory("tmpe_extract").toFile().getAbsolutePath();
        unArchiver.setDestDirectory(new File(tmpdir));
        ArchiverException exception = assertThrows(ArchiverException.class, unArchiver::extract);
        assertEquals("Entry is outside of the target directory (entry1)", exception.getMessage());
    }

cd ..
rm non_existing_symlink.zip
mkdir non_existing_symlink
cd non_existing_symlink
ln -s /tmp/target entry1
echo -ne 'content' > entry2
zip  --symlinks ../non_existing_symlink.zip entry1 entry2
cd ..
rm -rf non_existing_symlink
LC_ALL=C sed  -i '' 's/entry2/entry1/' non_existing_symlink.zip