hutool 解压缩读取源文件和压缩文件大小失败导致报错

前言

最近处理老项目中的问题,升级安全jar,发现hutool的jar在解压缩的时候报错了,实际上是很简单的防御zip炸弹攻击的手段,但是却因为hutool的工具包取文件大小有bug,造成了解压缩不能用,报错:invalid sizes: compressed -1, uncompressed -1,理论上使用这个API的所有方法都有问题。影响范围hutool 5.8.11~5.8.16,5.8.17修复。

Exception in thread "main" cn.hutool.core.exceptions.UtilException: Zip bomb attack detected, invalid sizes: compressed -1, uncompressed -1, name /Users/huahua/Downloads/zip-demo/1.exe
	at cn.hutool.core.compress.ZipReader.checkZipBomb(ZipReader.java:247)
	at cn.hutool.core.compress.ZipReader.readFromStream(ZipReader.java:224)
	at cn.hutool.core.compress.ZipReader.read(ZipReader.java:188)
	at cn.hutool.core.compress.ZipReader.readTo(ZipReader.java:148)
	at cn.hutool.core.compress.ZipReader.readTo(ZipReader.java:135)
	at cn.hutool.core.util.ZipUtil.unzip(ZipUtil.java:665)
	at cn.hutool.core.util.ZipUtil.unzip(ZipUtil.java:650)

demo准备

构建一个demo吧:JDK8+hutool 5.8.16

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        File file = new File("/Users/huahua/Downloads/zip-demo/1.exe");
        File zipFile = new File("/Users/huahua/Downloads/zip-demo/zip-demo.zip");
        ZipUtil.zip(zipFile, "/Users/huahua/Downloads/zip-demo/1.exe", new FileInputStream(file));

        ZipUtil.unzip(new FileInputStream(zipFile), new File("/Users/huahua/Downloads/zip-demo/2.exe"), null);

        System.out.println("Hello world!");
    }
}

没考虑流关闭问题,实际生产中使用try with resource即可

运行报错invalid sizes: compressed -1, uncompressed -1,这里的-1是文件大小,明显是取值不对

但是使用文件方式,确可以成功

从而确定是通过流的方式取文件大小是有问题的。 

原因

hutool实际上在5.8.10之前是没有检验zip炸弹的,从安全漏洞网站,可以看到出现:Hutool资源消耗漏洞 CVE-2022-4565Hutool资源消耗漏洞 CVE-2022-4565 - FreeBuf网络安全行业门户

为了解决这个漏洞,实际上就说zip炸弹攻击会消耗很多CPU资源,因为解压缩后要写文件,要存储,很可能造成DDOS和磁盘爆满。

引入了检查,默认是100倍的压缩比率,超过了也会报错,认为是zip炸弹,这个有点武断了,所以有个参数控制跳过,但是没有参数设置比率。直接从源码cn.hutool.core.compress.ZipReader分析

5.8.10版本,并没有检查zip压缩的比率,直接读取zip文件对象去解压了

	/**
	 * 读取并处理Zip流中的每一个{@link ZipEntry}
	 *
	 * @param consumer {@link ZipEntry}处理器
	 * @throws IORuntimeException IO异常
	 */
	private void readFromStream(Consumer<ZipEntry> consumer) throws IORuntimeException {
		try {
			ZipEntry zipEntry;
			while (null != (zipEntry = in.getNextEntry())) {
				consumer.accept(zipEntry);
			}
		} catch (IOException e) {
			throw new IORuntimeException(e);
		}
	}

升级5.8.11,按照100倍检查,超过100倍认为是zip炸弹,但是万一确实100倍怎么办,在5.8.21版本之前是没办法的,5.8.21做了跳过处理

	// size of uncompressed zip entry shouldn't be bigger of compressed in MAX_SIZE_DIFF times
	private static final int MAX_SIZE_DIFF = 100;
	/**
	 * 检查Zip bomb漏洞
	 *
	 * @param entry {@link ZipEntry}
	 * @return 检查后的{@link ZipEntry}
	 */
	private static ZipEntry checkZipBomb(ZipEntry entry) {
		if (null == entry) {
			return null;
		}
		final long compressedSize = entry.getCompressedSize();
		final long uncompressedSize = entry.getSize();
		if (compressedSize < 0 || uncompressedSize < 0 ||
				// 默认压缩比例是100倍,一旦发现压缩率超过这个阈值,被认为是Zip bomb
				compressedSize * MAX_SIZE_DIFF < uncompressedSize) {
			throw new UtilException("Zip bomb attack detected, invalid sizes: compressed {}, uncompressed {}, name {}",
					compressedSize, uncompressedSize, entry.getName());
		}
		return entry;
	}

 那么为什么5.8.16取文件流的文件大小都是-1呢,在5.8.17修复

因为文件构建的zipentry是有大小属性设置的,而从文件流读取的却丢失了大小属性

 

解决办法也很简单,从文件读取完成信息再检查,反正zip文件还没解压缩,这样zipentry就有文件大小属性了 

超过100倍大小

那么如果zip文件压缩比率超过100倍怎么办,只能升级hutool包,升级5.8.21试试,可以自定义大小倍数,且可以设置<0关闭检查

检查逻辑,以前的常亮改成了变量,且可自定义

但是,缺点依然明显,因为这个设置方法是对象方法,并没有开放配置的API,需要我们自己new 

ZipReader

来自定义设置,原来的API就不能使用了

 

总结

其实hutool工具包很方便,但是在笔者实际项目中经常会出现安全漏洞升级,笔者在分析完源码后在github也找到了相同的问题项:https://github.com/dromara/hutool/issues/3018

实际上很多人都遇到了,相似的jar还有guava经常出现API不兼容啥的,还有安全漏洞升级,不过还是感谢作者提供的开源便利。

猜你喜欢

转载自blog.csdn.net/fenglllle/article/details/142415599