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