You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用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的静态参数就行,完全不用反射:
    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"));
        }
    }
    
    当POI检测到解压后的文件大小超过阈值时,会直接抛出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

火山引擎 最新活动