This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch tomee-10.x in repository https://gitbox.apache.org/repos/asf/tomee.git
commit 7b9ed6ba3e9bf333957987093066b0cacc291373 Author: Richard Zowalla <[email protected]> AuthorDate: Tue Oct 13 08:49:07 2026 +0200 Reject archive entries escaping the target in tomee-maven-plugin (#3079) reject archive entries resolving outside catalina base, as Zips does (cherry picked from commit 8fb8e89deed464d308d24164045b66d2d1a57440) --- .../openejb/maven/plugin/AbstractTomEEMojo.java | 9 ++ .../org/apache/openejb/maven/plugin/UnzipTest.java | 104 +++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractTomEEMojo.java b/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractTomEEMojo.java index 9570ba839c..98535a888d 100644 --- a/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractTomEEMojo.java +++ b/maven/tomee-maven-plugin/src/main/java/org/apache/openejb/maven/plugin/AbstractTomEEMojo.java @@ -1751,6 +1751,8 @@ public abstract class AbstractTomEEMojo extends AbstractAddressMojo { private void unzip(final File mvnTomEE) { try (ZipFile in = new ZipFile(mvnTomEE)) { + final String destinationPath = catalinaBase.getCanonicalPath(); + final String destinationPrefix = destinationPath.endsWith(File.separator) ? destinationPath : destinationPath + File.separator; final Enumeration<? extends ZipEntry> entries = in.entries(); while (entries.hasMoreElements()) { @@ -1767,6 +1769,13 @@ public abstract class AbstractTomEEMojo extends AbstractAddressMojo { name = name.substring(idx + 1); } final File dest = new File(catalinaBase.getAbsolutePath(), name); + + // only extract entries resolving inside the destination directory + final String destPath = dest.getCanonicalPath(); + if (!destPath.equals(destinationPath) && !destPath.startsWith(destinationPrefix)) { + throw new IOException("Zip entry escapes the destination directory: " + entry.getName()); + } + if (!dest.exists()) { final File parent = dest.getParentFile(); if ((!parent.exists() && !parent.mkdirs()) diff --git a/maven/tomee-maven-plugin/src/test/java/org/apache/openejb/maven/plugin/UnzipTest.java b/maven/tomee-maven-plugin/src/test/java/org/apache/openejb/maven/plugin/UnzipTest.java new file mode 100644 index 0000000000..aa91da8e87 --- /dev/null +++ b/maven/tomee-maven-plugin/src/test/java/org/apache/openejb/maven/plugin/UnzipTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +package org.apache.openejb.maven.plugin; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class UnzipTest { + @Rule + public final TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void rejectsEntryEscapingCatalinaBase() throws Exception { + final File catalinaBase = folder.newFolder("base"); + final File zip = zip(folder.newFile("evil.zip"), "../evil.txt"); + + try { + unzip(mojo(catalinaBase, false), zip); + fail("entry escaping catalina base must be rejected"); + } catch (final InvocationTargetException e) { + assertTrue(String.valueOf(e.getCause()), e.getCause() instanceof TomEEException); + } + assertFalse(new File(folder.getRoot(), "evil.txt").exists()); + } + + @Test + public void rejectsEntryEscapingCatalinaBaseAfterRootFolderSkip() throws Exception { + final File catalinaBase = folder.newFolder("work", "base"); + final File zip = zip(folder.newFile("evil.zip"), "apache-tomee/../../evil.txt"); + + try { + unzip(mojo(catalinaBase, true), zip); + fail("entry escaping catalina base must be rejected"); + } catch (final InvocationTargetException e) { + assertTrue(String.valueOf(e.getCause()), e.getCause() instanceof TomEEException); + } + assertFalse(new File(folder.getRoot(), "evil.txt").exists()); + } + + @Test + public void extractsRegularEntries() throws Exception { + final File catalinaBase = folder.newFolder("base"); + final File zip = zip(folder.newFile("dist.zip"), "apache-tomee/conf/marker.txt"); + + unzip(mojo(catalinaBase, true), zip); + + assertTrue(new File(catalinaBase, "conf/marker.txt").isFile()); + } + + private static AbstractTomEEMojo mojo(final File catalinaBase, final boolean skipRootFolder) { + final AbstractTomEEMojo mojo = new AbstractTomEEMojo() { + @Override + public String getCmd() { + return "test"; + } + }; + mojo.catalinaBase = catalinaBase; + mojo.skipRootFolderOnUnzip = skipRootFolder; + mojo.overrideOnUnzip = true; + return mojo; + } + + private static void unzip(final AbstractTomEEMojo mojo, final File zip) throws Exception { + final Method unzip = AbstractTomEEMojo.class.getDeclaredMethod("unzip", File.class); + unzip.setAccessible(true); + unzip.invoke(mojo, zip); + } + + private static File zip(final File target, final String entryName) throws Exception { + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(target))) { + out.putNextEntry(new ZipEntry(entryName)); + out.write("content".getBytes(StandardCharsets.UTF_8)); + out.closeEntry(); + } + return target; + } +}
