Java Zip Slip漏洞案例分析及实战挖掘
字数 2266 2025-08-25 22:58:29

Java Zip Slip漏洞深度分析与实战指南

1. 漏洞概述

Zip Slip是一种文件解压路径遍历漏洞,主要出现在处理压缩文件解压功能的应用程序中。当应用程序解压用户可控的压缩文件时,如果未对压缩包中的文件名进行适当过滤,攻击者可以通过构造恶意文件名(如包含../序列)实现任意文件写入,可能导致服务器被完全控制。

漏洞特点

  • 影响广泛:在JavaScript、Ruby、.NET和Go等多种生态系统中都存在,但在Java中尤为普遍
  • 危害严重:可导致任意文件写入,进而可能实现远程代码执行
  • 触发场景:常见于文件上传并解压的功能点

2. 漏洞原理

2.1 核心漏洞机制

当应用程序解压zip文件时,通常会:

  1. 读取zip条目中的文件名
  2. 将该文件名与目标解压目录拼接
  3. 将文件内容写入拼接后的路径

如果未对文件名中的路径遍历序列(../)进行过滤,攻击者可以构造恶意zip文件,使其中的文件名包含路径遍历序列,从而将文件写入任意目录。

2.2 Java中的特殊问题

Java生态中缺乏一个提供高级归档文件处理功能的中央库,导致开发者经常手动编写或从社区(如StackOverflow)复制解压代码,而这些代码往往缺乏必要的安全检查。

3. 恶意Zip文件构造

3.1 Python构造示例

import zipfile

if __name__ == "__main__":
    try:
        zipFile = zipfile.ZipFile("poc.zip", "a", zipfile.ZIP_DEFLATED)
        info = zipfile.ZipInfo("poc.zip")
        zipFile.write("D:/tgao/pass/1", "../password", zipfile.ZIP_DEFLATED)
        zipFile.close()
    except IOError as e:
        raise e

3.2 构造说明

  • zipFile.write("D:/tgao/pass/1", "../password", zipfile.ZIP_DEFLATED)是关键
  • 第一个参数是本地要压缩的文件路径
  • 第二个参数是压缩包中的文件名,此处使用../password构造路径遍历
  • 解压时,该文件将被写入目标目录的上级目录中的password文件

4. 漏洞代码分析

4.1 原生java.util.zip漏洞示例

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class Zip1 {
    public static void main(String[] args) throws IOException {
        String fileAddress = "D:/pythonProject/exp/ctf/poc.zip";
        String unZipAddress = "D:/tgao/pass/";
        
        File file = new File(fileAddress);
        ZipFile zipFile = new ZipFile(file);
        
        Enumeration e = zipFile.entries();
        while(e.hasMoreElements()) {
            ZipEntry zipEntry = (ZipEntry)e.nextElement();
            // 漏洞点:直接拼接解压路径和条目名,未做过滤
            File f = new File(unZipAddress + zipEntry.getName());
            f.getParentFile().mkdirs();
            f.createNewFile();
            
            InputStream is = zipFile.getInputStream(zipEntry);
            FileOutputStream fos = new FileOutputStream(f);
            int length = 0;
            byte[] b = new byte[1024];
            while((length=is.read(b, 0, 1024))!=-1) {
                fos.write(b, 0, length);
            }
            is.close();
            fos.close();
        }
        zipFile.close();
    }
}

漏洞点分析

  • File f = new File(unZipAddress + zipEntry.getName())直接使用zip条目名拼接路径
  • 攻击者可控制zipEntry.getName()返回../malicious.jsp等恶意路径
  • 导致文件被写入任意目录

4.2 zt-zip组件漏洞

依赖引入

<dependency>
  <groupId>org.zeroturnaround</groupId>
  <artifactId>zt-zip</artifactId>
  <version>1.12</version>
</dependency>

漏洞代码

import org.zeroturnaround.zip.ZipUtil;
import java.io.File;

public class Zip2 {
    public static void main(String[] args) {
        File zip = new File("D:/pythonProject/exp/ctf/poc.zip");
        File dir = new File("D:/tgao/pass");
        ZipUtil.unpack(zip, dir);
    }
}

漏洞分析

  1. ZipUtil.unpack()内部使用IdentityNameMapper处理文件名
  2. IdentityNameMapper.map()方法直接返回原始文件名,不做任何过滤
  3. 最终调用原生Java API处理解压,导致路径遍历

修复情况

  • 在zt-zip 1.13版本中修复
  • 修复commit: https://github.com/zeroturnaround/zt-zip/commit/759b72f33bc8f4d69f84f09fcb7f010ad45d6fff

4.3 spring-integration-zip漏洞(CVE-2018-1261)

依赖引入

<dependency>
  <groupId>org.springframework.integration</groupId>
  <artifactId>spring-integration-zip</artifactId>
  <version>1.0.0.RELEASE</version>
</dependency>

漏洞代码

import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.zip.transformer.UnZipTransformer;
import org.springframework.messaging.Message;
import java.io.File;
import java.io.InputStream;

public class Zip3 {
    private static ResourceLoader resourceLoader = new DefaultResourceLoader();

    public static void main(String[] args) {
        final Resource evilResource = resourceLoader.getResource("classpath:poc.zip");
        try{
            InputStream evilIS = evilResource.getInputStream();
            Message<InputStream> evilMessage = MessageBuilder.withPayload(evilIS).build();
            UnZipTransformer unZipTransformer = new UnZipTransformer();
            unZipTransformer.transform(evilMessage);
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

漏洞分析

  1. UnZipTransformer内部使用zt-zip进行解压操作
  2. 创建匿名ZipEntryCallback对象处理文件时未做路径过滤
  3. 默认解压目录为系统临时目录下的ziptransformer文件夹
  4. 通过路径遍历可突破该目录限制

修复情况

  • 修复commit: https://github.com/spring-projects/spring-integration-extensions/commit/a5573eb232ff85199ff9bb28993df715d9a19a25

5. 审计实战案例

5.1 目标系统

DocSys文档管理系统 (https://gitee.com/RainyGao/DocSys)

5.2 漏洞代码

// com.DocSystem.controller.BaseController#unZip
while(entries.hasMoreElements()) {
    ZipEntry entry = (ZipEntry)entries.nextElement();
    File file = new File(destDir, entry.getName()); // 漏洞点
    if (entry.isDirectory()) {
        file.mkdirs();
    } else {
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        // ...文件写入操作
    }
}

5.3 触发路径

com.DocSystem.controller.ManageController#upgradeSystem方法调用了存在漏洞的unZip方法

5.4 利用方式

  1. 构造恶意zip文件,其中包含如../../webapp/malicious.jsp的文件
  2. 通过系统升级功能上传该zip文件
  3. 系统解压时会将恶意jsp文件写入web目录,实现RCE

5.5 漏洞复现参考

https://gitee.com/RainyGao/DocSys/issues/I65IYU

6. 防御方案

6.1 通用修复方法

  1. 路径规范化+验证

    String canonicalPath = file.getCanonicalPath();
    if (!canonicalPath.startsWith(destDirCanonicalPath)) {
        throw new SecurityException("Zip entry outside of target directory");
    }
    
  2. 使用安全库

    • 使用已修复的zt-zip(≥1.13)版本
    • 使用Apache Commons Compress等更安全的压缩库
  3. 文件名过滤

    String fileName = entry.getName();
    if (fileName.contains("../") || fileName.contains("..\\")) {
        throw new SecurityException("Invalid file path");
    }
    

6.2 各组件修复建议

  1. 原生Java.util.zip

    • 在拼接路径前验证文件名
    • 使用getCanonicalPath()检查最终路径是否在目标目录内
  2. zt-zip

    • 升级到1.13或更高版本
    • 或自定义安全的NameMapper实现
  3. spring-integration-zip

    • 升级到已修复版本
    • 或自定义安全的解压逻辑

7. 总结

Zip Slip是一个高危的路径遍历漏洞,在Java生态中尤为常见。开发者在实现解压功能时应注意:

  1. 永远不要信任压缩包中的文件名
  2. 必须对解压路径进行规范化并验证
  3. 优先使用已修复的安全版本库
  4. 在代码审计时特别关注ZipEntry.getName()的使用方式

通过本文的分析和案例,安全研究人员可以更有效地识别和修复此类漏洞,开发者可以避免在代码中引入此类安全问题。

Java Zip Slip漏洞深度分析与实战指南 1. 漏洞概述 Zip Slip是一种文件解压路径遍历漏洞,主要出现在处理压缩文件解压功能的应用程序中。当应用程序解压用户可控的压缩文件时,如果未对压缩包中的文件名进行适当过滤,攻击者可以通过构造恶意文件名(如包含 ../ 序列)实现任意文件写入,可能导致服务器被完全控制。 漏洞特点 影响广泛:在JavaScript、Ruby、.NET和Go等多种生态系统中都存在,但在Java中尤为普遍 危害严重:可导致任意文件写入,进而可能实现远程代码执行 触发场景:常见于文件上传并解压的功能点 2. 漏洞原理 2.1 核心漏洞机制 当应用程序解压zip文件时,通常会: 读取zip条目中的文件名 将该文件名与目标解压目录拼接 将文件内容写入拼接后的路径 如果未对文件名中的路径遍历序列( ../ )进行过滤,攻击者可以构造恶意zip文件,使其中的文件名包含路径遍历序列,从而将文件写入任意目录。 2.2 Java中的特殊问题 Java生态中缺乏一个提供高级归档文件处理功能的中央库,导致开发者经常手动编写或从社区(如StackOverflow)复制解压代码,而这些代码往往缺乏必要的安全检查。 3. 恶意Zip文件构造 3.1 Python构造示例 3.2 构造说明 zipFile.write("D:/tgao/pass/1", "../password", zipfile.ZIP_DEFLATED) 是关键 第一个参数是本地要压缩的文件路径 第二个参数是压缩包中的文件名,此处使用 ../password 构造路径遍历 解压时,该文件将被写入目标目录的上级目录中的password文件 4. 漏洞代码分析 4.1 原生java.util.zip漏洞示例 漏洞点分析 : File f = new File(unZipAddress + zipEntry.getName()) 直接使用zip条目名拼接路径 攻击者可控制zipEntry.getName()返回 ../malicious.jsp 等恶意路径 导致文件被写入任意目录 4.2 zt-zip组件漏洞 依赖引入 : 漏洞代码 : 漏洞分析 : ZipUtil.unpack() 内部使用 IdentityNameMapper 处理文件名 IdentityNameMapper.map() 方法直接返回原始文件名,不做任何过滤 最终调用原生Java API处理解压,导致路径遍历 修复情况 : 在zt-zip 1.13版本中修复 修复commit: https://github.com/zeroturnaround/zt-zip/commit/759b72f33bc8f4d69f84f09fcb7f010ad45d6fff 4.3 spring-integration-zip漏洞(CVE-2018-1261) 依赖引入 : 漏洞代码 : 漏洞分析 : UnZipTransformer 内部使用zt-zip进行解压操作 创建匿名 ZipEntryCallback 对象处理文件时未做路径过滤 默认解压目录为系统临时目录下的 ziptransformer 文件夹 通过路径遍历可突破该目录限制 修复情况 : 修复commit: https://github.com/spring-projects/spring-integration-extensions/commit/a5573eb232ff85199ff9bb28993df715d9a19a25 5. 审计实战案例 5.1 目标系统 DocSys文档管理系统 (https://gitee.com/RainyGao/DocSys) 5.2 漏洞代码 5.3 触发路径 com.DocSystem.controller.ManageController#upgradeSystem 方法调用了存在漏洞的 unZip 方法 5.4 利用方式 构造恶意zip文件,其中包含如 ../../webapp/malicious.jsp 的文件 通过系统升级功能上传该zip文件 系统解压时会将恶意jsp文件写入web目录,实现RCE 5.5 漏洞复现参考 https://gitee.com/RainyGao/DocSys/issues/I65IYU 6. 防御方案 6.1 通用修复方法 路径规范化+验证 : 使用安全库 : 使用已修复的zt-zip(≥1.13)版本 使用Apache Commons Compress等更安全的压缩库 文件名过滤 : 6.2 各组件修复建议 原生Java.util.zip : 在拼接路径前验证文件名 使用 getCanonicalPath() 检查最终路径是否在目标目录内 zt-zip : 升级到1.13或更高版本 或自定义安全的 NameMapper 实现 spring-integration-zip : 升级到已修复版本 或自定义安全的解压逻辑 7. 总结 Zip Slip是一个高危的路径遍历漏洞,在Java生态中尤为常见。开发者在实现解压功能时应注意: 永远不要信任压缩包中的文件名 必须对解压路径进行规范化并验证 优先使用已修复的安全版本库 在代码审计时特别关注 ZipEntry.getName() 的使用方式 通过本文的分析和案例,安全研究人员可以更有效地识别和修复此类漏洞,开发者可以避免在代码中引入此类安全问题。