mirror of https://github.com/apache/ant-ivy
CVE-2022-37865 ZipPacking allows overwriting arbitrary files
This commit is contained in:
parent
9d7b5e133f
commit
03b6b8c3ae
|
|
@ -52,8 +52,15 @@ public class ZipPacking extends ArchivePacking {
|
||||||
try (ZipInputStream zip = new ZipInputStream(packed)) {
|
try (ZipInputStream zip = new ZipInputStream(packed)) {
|
||||||
ZipEntry entry = null;
|
ZipEntry entry = null;
|
||||||
while (((entry = zip.getNextEntry()) != null)) {
|
while (((entry = zip.getNextEntry()) != null)) {
|
||||||
File f = new File(dest, entry.getName());
|
String entryName = entry.getName();
|
||||||
Message.verbose("\t\texpanding " + entry.getName() + " to " + f);
|
File f = FileUtil.resolveFile(dest, entryName);
|
||||||
|
if (!FileUtil.isLeadingPath(dest, f, true)) {
|
||||||
|
Message.verbose("\t\tskipping " + entryName + " as its target "
|
||||||
|
+ f.getCanonicalPath()
|
||||||
|
+ " is outside of " + dest.getCanonicalPath() + ".");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Message.verbose("\t\texpanding " + entryName + " to " + f);
|
||||||
|
|
||||||
// create intermediary directories - sometimes zip don't add them
|
// create intermediary directories - sometimes zip don't add them
|
||||||
File dirF = f.getParentFile();
|
File dirF = f.getParentFile();
|
||||||
|
|
|
||||||
|
|
@ -610,6 +610,68 @@ public final class FileUtil {
|
||||||
return new DissectedPath(File.separator, pathToDissect);
|
return new DissectedPath(File.separator, pathToDissect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Learn whether one path "leads" another.
|
||||||
|
*
|
||||||
|
* <p>This method uses {@link #normalize} under the covers and
|
||||||
|
* does not resolve symbolic links.</p>
|
||||||
|
*
|
||||||
|
* <p>If either path tries to go beyond the file system root
|
||||||
|
* (i.e. it contains more ".." segments than can be travelled up)
|
||||||
|
* the method will return false.</p>
|
||||||
|
*
|
||||||
|
* @param leading The leading path, must not be null, must be absolute.
|
||||||
|
* @param path The path to check, must not be null, must be absolute.
|
||||||
|
* @return true if path starts with leading; false otherwise.
|
||||||
|
* @since Ant 1.7
|
||||||
|
*/
|
||||||
|
public static boolean isLeadingPath(File leading, File path) {
|
||||||
|
String l = normalize(leading.getAbsolutePath()).getAbsolutePath();
|
||||||
|
String p = normalize(path.getAbsolutePath()).getAbsolutePath();
|
||||||
|
if (l.equals(p)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// ensure that l ends with a /
|
||||||
|
// so we never think /foo was a parent directory of /foobar
|
||||||
|
if (!l.endsWith(File.separator)) {
|
||||||
|
l += File.separator;
|
||||||
|
}
|
||||||
|
// ensure "/foo/" is not considered a parent of "/foo/../../bar"
|
||||||
|
String up = File.separator + ".." + File.separator;
|
||||||
|
if (l.contains(up) || p.contains(up) || (p + File.separator).contains(up)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return p.startsWith(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Learn whether one path "leads" another.
|
||||||
|
*
|
||||||
|
* @param leading The leading path, must not be null, must be absolute.
|
||||||
|
* @param path The path to check, must not be null, must be absolute.
|
||||||
|
* @param resolveSymlinks whether symbolic links shall be resolved
|
||||||
|
* prior to comparing the paths.
|
||||||
|
* @return true if path starts with leading; false otherwise.
|
||||||
|
* @since Ant 1.9.13
|
||||||
|
* @throws IOException if resolveSymlinks is true and invoking
|
||||||
|
* getCanonicaPath on either argument throws an exception
|
||||||
|
*/
|
||||||
|
public static boolean isLeadingPath(File leading, File path, boolean resolveSymlinks)
|
||||||
|
throws IOException {
|
||||||
|
if (!resolveSymlinks) {
|
||||||
|
return isLeadingPath(leading, path);
|
||||||
|
}
|
||||||
|
final File l = leading.getCanonicalFile();
|
||||||
|
File p = path.getCanonicalFile();
|
||||||
|
do {
|
||||||
|
if (l.equals(p)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
p = p.getParentFile();
|
||||||
|
} while (p != null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the length of the file, or the sum of the children lengths if it is a directory
|
* Get the length of the file, or the sum of the children lengths if it is a directory
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import static org.junit.Assert.assertTrue;
|
||||||
public class FileUtilTest {
|
public class FileUtilTest {
|
||||||
|
|
||||||
private static boolean symlinkCapable = false;
|
private static boolean symlinkCapable = false;
|
||||||
|
private static final String PATH_SEP = System.getProperty("path.separator");
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeClass() {
|
public static void beforeClass() {
|
||||||
|
|
@ -151,4 +152,75 @@ public class FileUtilTest {
|
||||||
Assert.assertTrue("Unexpected content in dest file " + destFile, Arrays.equals(fileContent, Files.readAllBytes(destFile)));
|
Assert.assertTrue("Unexpected content in dest file " + destFile, Arrays.equals(fileContent, Files.readAllBytes(destFile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see "https://bz.apache.org/bugzilla/show_bug.cgi?id=62502"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void isLeadingPathCannotBeFooledByTooManyDoubleDots() {
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/../../bar")));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("c:\\foo"), new File("c:\\foo\\..\\..\\bar")));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/../..")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see "https://bz.apache.org/bugzilla/show_bug.cgi?id=62502"
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void isLeadingPathCanonicalVersionCannotBeFooledByTooManyDoubleDots() throws IOException {
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/../../bar"), true));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("c:\\foo"), new File("c:\\foo\\..\\..\\bar"), true));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/../.."), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isLeadingPathCanonicalVersionWorksAsExpectedOnUnix() throws IOException {
|
||||||
|
Assume.assumeFalse("Test doesn't run on DOS", PATH_SEP.equals(";"));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/bar"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/baz/../bar"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/../foo/bar"), true));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("/foo"), new File("/foobar"), true));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("/foo"), new File("/bar"), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isLeadingPathAndTrailingSlashesOnUnix() throws IOException {
|
||||||
|
Assume.assumeFalse("Test doesn't run on DOS", PATH_SEP.equals(";"));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo/bar"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo/bar/"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo/"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/"), true));
|
||||||
|
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo/bar"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo/bar/"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo/"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo/"), new File("/foo"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("/foo"), new File("/foo/"), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isLeadingPathCanonicalVersionWorksAsExpectedOnDos() throws IOException {
|
||||||
|
Assume.assumeTrue("Test only runs on DOS", PATH_SEP.equals(";"));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("C:\\foo"), new File("C:\\foo\\bar"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("C:\\foo"), new File("C:\\foo\\baz\\..\\bar"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("C:\\foo"), new File("C:\\foo\\..\\foo\\bar"), true));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("C:\\foo"), new File("C:\\foobar"), true));
|
||||||
|
Assert.assertFalse(FileUtil.isLeadingPath(new File("C:\\foo"), new File("C:\\bar"), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isLeadingPathAndTrailingSlashesOnDos() throws IOException {
|
||||||
|
Assume.assumeTrue("Test only runs on DOS", PATH_SEP.equals(";"));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo\\bar"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo\\bar\\"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo\\"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo"), true));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo"), new File("c:\\foo\\"), true));
|
||||||
|
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo\\bar"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo\\bar\\"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo\\"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo\\"), new File("c:\\foo"), false));
|
||||||
|
Assert.assertTrue(FileUtil.isLeadingPath(new File("c:\\foo"), new File("c:\\foo\\"), false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.ivy.core.pack;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
import org.apache.ivy.TestHelper;
|
||||||
|
import org.apache.ivy.util.DefaultMessageLogger;
|
||||||
|
import org.apache.ivy.util.FileUtil;
|
||||||
|
import org.apache.ivy.util.Message;
|
||||||
|
import org.apache.tools.ant.Project;
|
||||||
|
import org.apache.tools.ant.taskdefs.Mkdir;
|
||||||
|
import org.apache.tools.ant.taskdefs.Delete;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ZipPackingTest {
|
||||||
|
|
||||||
|
private static final Project PROJECT = TestHelper.newProject();
|
||||||
|
private static final File TEST_DIR = PROJECT.resolveFile("build/test/pack");
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
Mkdir mkdir = new Mkdir();
|
||||||
|
mkdir.setProject(PROJECT);
|
||||||
|
mkdir.setDir(TEST_DIR);
|
||||||
|
mkdir.execute();
|
||||||
|
Message.setDefaultLogger(new DefaultMessageLogger(Message.MSG_INFO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
Delete del = new Delete();
|
||||||
|
del.setProject(PROJECT);
|
||||||
|
del.setDir(TEST_DIR);
|
||||||
|
del.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void zipPackingExtractsArchive() throws IOException {
|
||||||
|
try (InputStream zip = new FileInputStream(PROJECT.resolveFile("test/zip/test.zip"))) {
|
||||||
|
new ZipPacking().unpack(zip, TEST_DIR);
|
||||||
|
}
|
||||||
|
assertTrue("Expecting file a", FileUtil.resolveFile(TEST_DIR, "a").isFile());
|
||||||
|
assertTrue("Expecting directory b", FileUtil.resolveFile(TEST_DIR, "b").isDirectory());
|
||||||
|
assertTrue("Expecting file b/c", FileUtil.resolveFile(TEST_DIR, "b/c").isFile());
|
||||||
|
assertTrue("Expecting directory d", FileUtil.resolveFile(TEST_DIR, "d").isDirectory());
|
||||||
|
assertFalse("Not expecting file e", PROJECT.resolveFile("build/test/e").exists());
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in New Issue