JavaWeb解压缩漏洞之ZipSlip与Zip炸弹

前言

前面一篇博文《Android Zip解压缩目录穿越导致文件覆盖漏洞》介绍过 Android 系统 Zip 文件解压缩场景下的目录穿越漏洞,近期在学习 JavaWeb 代码审计的时候从 github 看到《OpenHarmony-Java-secure-coding-guide.md》中“从 ZipInputStream 中解压文件必须进行安全检查”章节提及 JavaWeb 系统同样涉及此类目录穿越漏洞,同时还涉及 Zip 炸弹的攻击场景,故在此学习记录下。

Zip Slip

此类漏洞指的是解压 zip 文件时没有校验各解压文件的名字,如果文件名包含 ../ 会导致解压文件被释放到目标目录之外的目录。

在《Android Zip解压缩目录穿越导致文件覆盖漏洞》一文已经讲过原理,不再过多赘述。直接沿用原来的恶意 Zip 生成代码和 Zip 解压缩代码即可:

import zipfile


def zip_slip_file(output_path):
    try:
        with open("source/test.txt", "r") as f:
            binary = f.read()
            zipFile = zipfile.ZipFile(output_path, "a", zipfile.ZIP_DEFLATED)
            zipFile.writestr("../../test.txt", binary)
            zipFile.close()
    except Exception as e:
        print(e)


if __name__ == '__main__':
    zip_slip_file(r'result/test.zip')
    public static void unzipFile(String zipPtath, String outputDirectory) throws IOException {
        File file = new File(outputDirectory);
        if (!file.exists()) {
            file.mkdirs();
        }
        InputStream inputStream = new FileInputStream(zipPtath); ;
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        byte[] buffer = new byte[1024 * 1024];
        int count;
        ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null){
            if (!zipEntry.isDirectory()) {
                String fileName = zipEntry.getName();
                System.out.println("解压文件的名字: " + fileName + ",解压文件的大小: " + zipEntry.getSize());
                file = new File(outputDirectory + File.separator + fileName);
                file.createNewFile();
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                while ((count = zipInputStream.read(buffer)) > 0) {
                    fileOutputStream.write(buffer, 0, count);
                }
                fileOutputStream.close();
            }
        }
        zipInputStream.close();
        System.out.println("解压完成!");
    }

运行结果如下,成功进行路径穿越:
image.png
在实际漏洞利用中,可借助上述 Zip Slip 漏洞,对系统重要文件或可执行文件进行被覆盖,从而造成系统故障或任意代码执行的危害。

Zip 炸弹

重点介绍下 Zip 炸弹,先看下 OpenHarmony Java 安全编码指导文档的相关描述:
image.png
Zip 炸弹的大致原理是 zip 炸弹文件中有大量刻意重复的数据,这种重复数据在压缩的时候是可以被丢弃的,这也就是压缩后的文件其实并不大的原因。最为典型的 Zip 炸弹就是 42.zip,一个 42KB 的文件,解压完其实是个 4.5 PB(1 PB=1024 TB) 的“炸弹”,详细原理可参见:A better zip bomb。

漏洞演示

Github 有生成 Zip 炸弹的现成项目: CreeperKong/zipbomb-generator。

脚本用法很简单,如下指定生成包含一个 3.9G 左右大小的 test.zip 文件:

python zipbomb.py --mode=quoted_overlap --num-files=1 --compressed-size=3999999 > test.zip

image.png
修改 --num-files=10 参数则可以令 zip 中包含 10 个重复的上述文件(当然还可以包含更多):
image.pngimage.png
由上面可见,如果 Web 服务器从客户端发送过来的 http 报文中提取 zip 文件并进行解压缩的时候没校验 zip 文件夹内部文件的大小的话,将导致攻击者可以传递 zip 炸弹耗尽服务器资源,形成严重的 Dos 攻击。

历史上知名组件的相关漏洞的话可以参见 ZIP bomb vulnerability in HuTool:
image.png

错误修补

上文提到使用 zipEntry.getSize() 函数获取 zip 文件大小是不可取,zipEntry.getSize()是从 zip 文件中的固定字段中读取单个文件压缩前的大小,如何篡改并欺骗服务器?

先模仿一段存在缺陷的修复代码:

    public static void unzipFile(String zipPtath, String outputDirectory) throws IOException {
        File file = new File(outputDirectory);
        if (!file.exists()) {
            file.mkdirs();
        }
        InputStream inputStream = new FileInputStream(zipPtath); ;
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        byte[] buffer = new byte[1024 * 1024];
        int count;
        ZipEntry zipEntry;
        while ((zipEntry = zipInputStream.getNextEntry()) != null){
            if (!zipEntry.isDirectory()) {
                String fileName = zipEntry.getName();
                System.out.println("解压文件的名字: " + fileName + ",解压文件的大小: " + zipEntry.getSize());
                // 判断被压缩的文件的大小,单个文件不得大于4Mb
                if(zipEntry.getSize() < 4096){
                    file = new File(outputDirectory + File.separator + fileName);
                    file.createNewFile();
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    while ((count = zipInputStream.read(buffer)) > 0) {
                        fileOutputStream.write(buffer, 0, count);
                    }
                    fileOutputStream.close();
                }else {
                    System.out.println("文件大小超出限制!");
                    return;
                }
            }
        }
        zipInputStream.close();
        System.out.println("解压完成!");
    }

image.png
上述代码判断被压缩的文件的大小,单个文件不得大于 4Mb,如何绕过?

步骤很简单,首先下载用于修改二进制文件的 010editor 软件,安装后打开上面演示用的 Zip 炸弹 test.zip(包含了一个 3.9G 的大文件):
image.png
image.png
修改图示 frUncompressedsize 字段的值后,重新运行 Java 程序对其进行解压缩,可成功绕过文件大小限制,解压出目标文件:
image.png
用 VSCode 可成功打开上述解压缩出来的文件(文件内容全都是 aaaaa……),意味着文件并未损坏:
image.png
可以看到,此时zipEntry.getSize() 函数获取到的压缩文件大小已经变成我们修改完以后的值(10),同时成功解压缩出 3.9G 大小的目标文件,成功绕过了修复代码对于压缩文件的大小限制。

值得注意的是,从上述截图也可以看到修改了 zip 文件的 frUncompressedsize 字段的值以后,解压缩 zip 文件会报错,如果直接使用 7-zip 进行解压缩的话更是直接报错而终止,提取不出任何文件:

java.util.zip.ZipException: invalid entry size (expected 10 but got 396289 bytes)
	at java.util.zip.ZipInputStream.readEnd(ZipInputStream.java:384)
	at java.util.zip.ZipInputStream.read(ZipInputStream.java:196)
	at java.io.FilterInputStream.read(FilterInputStream.java:107)
	at Util.Util.unzipFile(Util.java:42)
	at Main.main(Main.java:14)

image.png
但是通过实践也可以看到,通过上述 Java 代码可成功解压缩出来目标文件,这样子的话就不影响我们通过修改 zip 文件的 frUncompressedsize 字段的值,制作 zip 炸弹绕过服务端的文件大小校验检测,完成攻击利用。

安全编码

最后直接看看《OpenHarmony-Java-secure-coding-guide》提供的 Zip 文件解压缩的安全编码示例:

private static final long MAX_FILE_COUNT = 100L;
private static final long MAX_TOTAL_FILE_SIZE = 1024L * 1024L;

...

public void unzip(FileInputStream zipFileInputStream, String dir) throws IOException {
    long fileCount = 0;
    long totalFileSize = 0;
    try (ZipInputStream zis = new ZipInputStream(zipFileInputStream)) {
        ZipEntry entry;
        String entryName;
        String entryFilePath;
        File entryFile;
        byte[] buf = new byte[10240];
        int length;
        while ((entry = zis.getNextEntry()) != null) {
            entryName = entry.getName();
            //先对文件名的合法性进行校验
            entryFilePath = sanitizeFileName(entryName, dir);
            entryFile = new File(entryFilePath);
            if (entry.isDirectory()) {
                creatDir(entryFile);
                continue;
            }
            fileCount++;
            //对zip压缩包中的文件数量进行限制,设置了上限阈值
            if (fileCount > MAX_FILE_COUNT) {
                throw new IOException("The ZIP package contains too many files.");
            }
            //此处不再同通过zipEntry.getSize()函数获取 zip 文件大小,而是通过文件数据流直接读取整个文件的数据并统计大小
            try (FileOutputStream fos = new FileOutputStream(entryFile)) {
                while ((length = zis.read(buf)) != -1) {
                    totalFileSize += length;
                    zipBombCheck(totalFileSize);
                    fos.write(buf, 0, length);
                }
            }
        }
    }
}

//防止压缩文件名携带../导致的Zip Slip路径穿越漏洞
private String sanitizeFileName(String fileName, String dir) throws IOException {
    File file = new File(dir, fileName);
    String canonicalPath = file.getCanonicalPath();
    if (canonicalPath.startsWith(dir)) {
        return canonicalPath;
    }
    throw new IOException("Path Traversal vulnerability: ...");
}

private void creatDir(File dirPath) throws IOException {
    boolean result = dirPath.mkdirs();
    if (!result) {
        throw new IOException("Create dir failed, path is : " + dirPath.getPath());
    }
    ...
}

//防止zip炸弹
private void zipBombCheck(long totalFileSize) throws IOException {
    if (totalFileSize > MAX_TOTAL_FILE_SIZEG) {
        throw new IOException("Zip Bomb! The size of the file extracted from the ZIP package is too large.");
    }
}

上述示例中,一共做了 3 项目安全检查:

  1. 在解压每个文件之前对其文件名进行校验,如果校验失败,整个解压过程会被终止,防止路径穿越漏洞;
  2. 解压缩过程中,对每个文件通过文件数据流识别其实际大小,如果达到指定的阈值(MAX_TOTAL_FILE_SIZE),会抛出异常终止解压操作;
  3. 同时,程序会统计解压出来的文件的数量,如果达到指定阈值(MAX_FILE_COUNT),会抛出异常终止解压操作。

总结

从上面的安全示例编码可以看到,简简单单的一个常见 Zip 文件解压缩过程,需要做的安全校验却并不少。总的来说,研发人员在编写对用户可见的 zip 文件上传功能时,一定要严格校验好 zip 文件中待解压缩的文件文件名是否包含../非法字符,校验带解压的文件大小,同时禁止通过 zipEntry.getSize() 函数获取 zip 文件大小,最后也需要校验下解压缩出来的文件总数(设置阈值,毕竟积少成多,通过大量中小型文件也可以完成 zip 炸弹攻击)。

本文参考文章:

  1. Java代码审计指南;
  2. OpenHarmony-Java-secure-coding-guide;
  3. 压缩炸弹(zipbomb)制作(附演示);
  4. 一个42KB的文件,是如何解压完变成一个4.5PB的数据;
  5. https://github.com/CreeperKong/zipbomb-generator;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/494730.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

搭建机器人产业发展重要展示平台“2024南京国际机器人展览会”

2024南京国际智能机器人展览会 2024 Nanjing Intelligent Robot Expo 时间:2024年11月22-24日 地点:南京国际博览中心 南京&#xff0c;这座历史悠久的文化名城&#xff0c;如今正站在机器人产业发展的前沿。随着全球科技的飞速进步&#xff0c;机器人产业已经成为推动经济社…

记一次由gzip引起的nginx转发事故

故事背景 书接前几篇文章&#xff0c;仍然是交付甲方遇到的一个特殊诉求&#xff0c;从而引发了本期的事故。甲方的诉求是前端的请求过来&#xff0c;需要加密&#xff0c;但是要经过waf&#xff0c;必须要求是请求明文&#xff0c;那就要在waf和nginx之间做一个解密前置应用处…

网络链路层之(2)PPP协议

网络链路层之(2)PPP协议 Author: Once Day Date: 2024年3月27日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客-CSDN…

pt-archiver的实践分享,及为何要用 ob-archiver 归档数据的探讨

作者简介&#xff1a;肖杨&#xff0c;软件开发工程师 在数据密集型业务场景中&#xff0c;数据管理策略是否有效至关重要&#xff0c;它直接关系到系统性能与存储效率的提升。数据归档作为该策略的关键环节&#xff0c;不仅有助于优化数据库性能&#xff0c;还能有效降低存储成…

(一)基于IDEA的JAVA基础9

循环结构及特点 while循环 do while循环 for 循环 特点:有循环条件 循环起始值 循环自增量(每次自增的量) 循环操作 while循环 语法: 初始部分//定义变量的地方&#xff0c;声明变量 while(循环条件){ 循环操作&#xff1b; 迭代部分&#xff1b; } 流程图: 练习:打…

Rust编程(三)生命周期与异常处理

生命周期 生命周期&#xff0c;简而言之就是引用的有效作用域。在大多数时候&#xff0c;我们无需手动的声明生命周期&#xff0c;因为编译器可以自动进行推导。生命周期的主要作用是避免悬垂引用&#xff0c;它会导致程序引用了本不该引用的数据&#xff1a; {let r;{let x …

【办公类-21-11】 20240327三级育婴师 多个二级文件夹的docx合并成docx有页码,转PDF

背景展示&#xff1a;有页码的操作题 背景需求&#xff1a; 实操课终于全部结束了&#xff0c;把考试内容&#xff08;docx&#xff09;都写好了 【办公类-21-10】三级育婴师 视频转文字docx&#xff08;等线小五单倍行距&#xff09;&#xff0c;批量改成“宋体小四、1.5倍行…

2024 MCM数学建模美赛2024年A题复盘,思路与经验分享:资源可用性与性别比例 | 性别比例变化是否对生态系统中的其他生物如寄生虫提供优势(五)

审题 第四问让我们探究性别比例变化是否对生态系统中的其他生物如寄生虫提供优势。这里我们可以把问题简化一下&#xff0c;只探究性别比例会不会对寄生虫提供优势。因为考虑太多生物&#xff0c;会使模型更复杂&#xff0c;我这个水平处理不了这么复杂的问题&#xff0c;是我…

整数在内存里面的存储

整数在内存里面的存储 整数在计算机里面的存储是按照二进制的方式进行存储 显示的时候是按照16进制的方法进行显示 1. 整数在内存中的存储在讲解操作符的时候&#xff0c;我们就讲过了下⾯的内容&#xff1a;整数的2进制表⽰⽅法有三种&#xff0c;即原码、反码和补码 三种…

案例研究|DataEase实现物业数据可视化管理与决策支持

河北隆泰物业服务有限责任公司&#xff08;以下简称为“隆泰物业”&#xff09;创建于2002年&#xff0c;总部设在河北省高碑店市&#xff0c;具有国家一级物业管理企业资质&#xff0c;通过了质量体系、环境管理体系、职业健康安全管理体系等认证。自2016年至今&#xff0c;隆…

FIM配置

FIM&#xff08;功能抑制管理器&#xff09; FIM模块根据DTC状态来确定对应功能是否要禁止 FiM_GetFunctionPermission通过RTE提供给SWC FiMFIDs FiMInhibitionConfigurations FiMInhFunctionIdRef&#xff1a;关联FIMID FiMInhInhibitionMask: FIM_LAST_FAILED Inh Event…

【氮化镓】p-GaN栅极退化的温度和结构相关性

论文总结&#xff1a; 本文献深入研究了带有p-GaN栅极的正常关断型(normally-off)高电子迁移率晶体管(GaN-HEMTs)在恒定电压应力下的时序退化行为。通过直流特性分析和温度依赖性分析&#xff0c;研究了故障时间(TTF)与应力温度和器件几何结构的依赖性。结果显示&#xff0c;p…

算法打卡day19

今日任务&#xff1a; 1&#xff09;235. 二叉搜索树的最近公共祖先 2&#xff09;701.二叉搜索树中的插入操作 3&#xff09;450.删除二叉搜索树中的节点 235. 二叉搜索树的最近公共祖先 题目链接&#xff1a;235. 二叉搜索树的最近公共祖先 - 力扣&#xff08;LeetCode&…

Android 自定义EditText

文章目录 Android 自定义EditText概述源码可清空内容的EditText可显示密码的EditText 使用源码下载 Android 自定义EditText 概述 定义一款可清空内容的 ClearEditText 和可显示密码的 PasswordEditText&#xff0c;支持修改提示图标和大小、背景图片等。 源码 基类&#xf…

大语言模型(LLM)token解读

1. 什么是token&#xff1f; 人们经常在谈论大模型时候&#xff0c;经常会谈到模型很大&#xff0c;我们也常常会看到一种说法&#xff1a; 参数会让我们了解神经网络的结构有多复杂&#xff0c;而token的大小会让我们知道有多少数据用于训练参数。 什么是token&#xff1f;比…

【C语言】Infiniband驱动init_dev_assign函数

一、注释 一个内核模块的初始化函数&#xff0c;用于分配和初始化某些资源。以下是对代码块的逐行中文注释&#xff1a; // 定义一个初始化设备分配的函数 static void init_dev_assign(void) {int i 1;spin_lock_init(&dev_num_str_lock); // 初始化自旋锁if (mlx4_fil…

量化交易入门(二十三)什么是MTM指标,原理是什么

MTM指标全称是Momentum指标,翻译为动量指标。它用来衡量市场价格在一定时间内上涨或下跌的幅度,属于趋势型指标。其计算公式是: MTM(N) 当前收盘价 - N日前的收盘价 其中N表示统计的周期数,常用参数有6日、12日和24日。 MTM指标的应用要点如下: 判断趋势强弱:MTM数值越大,表…

泛型的进阶

1 通配符 &#xff1f; 我们想调用fun函数帮我们打印&#xff0c;但由于不知道Message具体是什么类型&#xff0c;所以我们可以使用 &#xff1a; &#xff1f;即通配符 当我们将fun函数中改为Message<?>此时就不会报错 2 通配符的上界&#xff1a; <? extends 上…

如何使用 ArcGIS Pro 自动矢量化水系

对于某些要素颜色统一的地图&#xff0c;比如电子地图&#xff0c;可以通过图像识别技术将其自动矢量化&#xff0c;这里为大家介绍一下 ArcGIS Pro 自动矢量化水系的方法&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的电子地图数据&#…

二分练习题——123

123 二分等差数列求和前缀和数组 题目分析 连续一段的和我们想到了前缀和&#xff0c;但是这里的l和r的范围为1e12&#xff0c;明显不能用O(n)的时间复杂度去求前缀和。那么我们开始观察序列的特点&#xff0c;可以按照等差数列对序列进行分块。如上图&#xff0c;在求前10个…