如何使用Java 10检测Zip炸弹?适配Apache POI的防护需求
在Java 10中检测Zip炸弹的实用方案
刚好之前研究过Java 10环境下的Zip炸弹检测问题——毕竟Java 10引入了模块系统,之前Java 9及更早用的反射变通方法不太好使了。结合Apache POI的官方适配和原生Java API的实现,给你整理几个靠谱的方案:
1. 用Apache POI的原生防护(最推荐)
Apache POI从4.0版本开始就适配了Java 9+的模块系统,不再依赖反射来调整Zip炸弹防护的参数了。你可以直接通过两种方式配置防护阈值:
- 全局JVM配置:启动程序时添加系统属性,比如设置单个解压文件最大100MB,总解压大小上限1GB:
-Dorg.apache.poi.util.ZipArchiveThreshold.MAX_ENTRY_SIZE=104857600 -Dorg.apache.poi.util.ZipArchiveThreshold.MAX_TOTAL_SIZE=1073741824 - 代码内动态配置:如果需要在程序里灵活调整,直接修改
ZipArchiveThreshold的静态参数就行,完全不用反射:
当POI检测到解压后的文件大小超过阈值时,会直接抛出import org.apache.poi.util.ZipArchiveThreshold; public class POIZipBombGuard { public static void main(String[] args) { // 单个条目最大解压大小设为50MB ZipArchiveThreshold.setMaxEntrySize(50 * 1024 * 1024); // 总解压大小上限设为500MB ZipArchiveThreshold.setMaxTotalSize(500 * 1024 * 1024); // 之后正常用POI读写Excel/Word就行,比如: // Workbook workbook = WorkbookFactory.create(new File("target.xlsx")); } }ZipBombException,你捕获这个异常就能拦截恶意文件了。
2. 手动实现检测逻辑(不依赖POI)
如果你不需要用POI,想自己写检测逻辑,核心思路是监控压缩包的解压后大小和压缩比例:
- 先遍历每个Zip条目,对比压缩前后的大小,设置一个合理的比例阈值(比如100:1,超过就可疑);
- 同时累计所有条目的总解压大小,超过上限就终止操作;
- 额外注意:有些恶意文件会伪造解压大小的元数据,所以最好实际读取内容验证大小,避免被坑。
给你个示例代码:
import java.io.FileInputStream; import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class ManualZipBombChecker { // 单个条目最大解压大小:100MB private static final long MAX_ENTRY_SIZE = 100 * 1024 * 1024; // 总解压大小上限:1GB private static final long MAX_TOTAL_SIZE = 1024 * 1024 * 1024; // 可疑压缩比例:超过100倍就预警 private static final int SUSPICIOUS_RATIO = 100; public static void checkZipSafety(String filePath) throws IOException { long totalUncompressed = 0; try (ZipInputStream zis = new ZipInputStream(new FileInputStream(filePath))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { if (entry.isDirectory()) { zis.closeEntry(); continue; } long uncompressedSize = entry.getSize(); long compressedSize = entry.getCompressedSize(); // 单个条目超上限直接抛出异常 if (uncompressedSize > MAX_ENTRY_SIZE) { throw new IOException("Zip炸弹预警:单个文件解压后超过大小限制"); } // 计算压缩比例,避免除以0 if (compressedSize > 0 && uncompressedSize / compressedSize > SUSPICIOUS_RATIO) { throw new IOException("Zip炸弹预警:压缩比例异常过高"); } totalUncompressed += uncompressedSize; // 总大小超上限直接终止 if (totalUncompressed > MAX_TOTAL_SIZE) { throw new IOException("Zip炸弹预警:总解压大小超过限制"); } // 可选:实际读取内容验证大小,防止元数据伪造 byte[] buffer = new byte[8192]; long bytesRead = 0; int read; while ((read = zis.read(buffer)) != -1) { bytesRead += read; if (bytesRead > MAX_ENTRY_SIZE) { throw new IOException("Zip炸弹预警:读取时发现文件大小超标"); } } // 验证实际读取大小和元数据是否一致 if (uncompressedSize != -1 && bytesRead != uncompressedSize) { throw new IOException("Zip文件异常:大小元数据与实际不符"); } zis.closeEntry(); } } } public static void main(String[] args) { try { checkZipSafety("test.zip"); System.out.println("这个Zip文件是安全的"); } catch (IOException e) { System.err.println("检测到问题:" + e.getMessage()); } } }
3. 用Java 10+的ZipFileSystem API
Java 7引入的ZipFileSystem可以更简洁地遍历Zip内容,结合大小检测也能实现防护:
import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class ZipFsBombChecker { private static final long MAX_TOTAL_SIZE = 1024 * 1024 * 1024; // 1GB public static void checkZip(String filePath) throws IOException { Path zipPath = Paths.get(filePath); try (var zipFs = FileSystems.newFileSystem(zipPath, null)) { long totalSize = 0; // 遍历所有非目录文件 for (Path file : Files.walk(zipFs.getPath("/")).filter(Files::isRegularFile).toList()) { long size = Files.size(file); totalSize += size; if (totalSize > MAX_TOTAL_SIZE) { throw new IOException("Zip炸弹预警:总解压大小超过限制"); } } } } }
这个方法代码更简洁,但同样要注意恶意文件伪造大小的问题,必要时还是要读取内容验证。
内容的提问来源于stack exchange,提问作者centic




