前言
最近处理老项目中的问题,升级安全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.exeat 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 timesprivate 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 bombcompressedSize * 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不兼容啥的,还有安全漏洞升级,不过还是感谢作者提供的开源便利。