转载

spring-integration-zip-unsafe-unzip分析

看pivotal发布的漏洞信息如下:

spring-integration-zip-unsafe-unzip分析

通过关键字信息可以看出来,这个漏洞是因为没有对解压的zip中的文件和目录进行确认,导致在解压zip包时可能会存在任意目录文件写入的漏洞。这个漏洞主要与 unzip transformer 漏洞相关。漏洞的版本需小于 1.0.1 版本。

漏洞分析

环境搭建

本实验的代码使用的是chybeta师傅提供的代码, 下载地址

根据漏洞存在的版本,修改pom.xml文件中的 spring-integration-zip1.0.1 版本。

spring-integration-zip-unsafe-unzip分析

恶意zip文件

恶意的zip文件的结构如下所示:

spring-integration-zip-unsafe-unzip分析

在当前zip文件中,存在 good.txt 文件以及 ../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../tmp 目录,tmp目录下面存在 evil.txt 文件。

前提知识

在漏洞漏洞之前,需要对 spring-integration-zip 中的 ZipInputStreamZipEntry 有一个简单的认识。通过zip的结构我们可以知道,需要通过zip中的目录名作为目录穿越的payload。通过以下实例代码来了解 ZipEntry 的用法。

File file = new File("D://zip-malicious-traversal.zip");
ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = null;
while ( (entry = zis.getNextEntry()) != null ) {
    System.out.println( entry.getName());
}

通过 ZipEntrygetName() 输出的是:

good.txt
../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../tmp/evil.txt

所以 ZipEntrygetName() 会得到zip包中的目录名以及其中的文件名。

漏洞分析

项目的目录结构如下所示:

spring-integration-zip-unsafe-unzip分析

我们程序的测试代码如下:

private static ResourceLoader resourceLoader = new DefaultResourceLoader();
private static File path =  new File("./here/");
public static void main(final String... args) {
    final Resource evilResource = resourceLoader.getResource("classpath:zip-malicious-traversal.zip");
    try{
        InputStream evilIS = evilResource.getInputStream();
        Message<InputStream> evilMessage = MessageBuilder.withPayload(evilIS).build();
        UnZipTransformer unZipTransformer = new UnZipTransformer();
        unZipTransformer.setWorkDirectory(path);
        unZipTransformer.afterPropertiesSet();
        unZipTransformer.transform(evilMessage);
    }catch (Exception e){
        System.out.println(e);
    }
}

解压 zip-malicious-traversal.zip ,将解压之后的文件写入到父目录中的 hehe 目录中。漏洞的关键代码位于 unZipTransformer.transform(evilMessage);

跟踪代码 unZipTransformer.transform(evilMessage); ,进入到 org.springframework.integration.zip.transformer.UnZipTransformer:doZipTransform() 中。当程序运行至68行,分析此时的参数。

spring-integration-zip-unsafe-unzip分析

inputStream 中含有恶意的zip文件,而 ZipEntryCallback() 作为回调函数进一步对zip包进行处理。首先分析 ZipUtil.iterate() 函数,进入到 org.zeroturnaround.zip.ZipUtil:iterate() 中。

public static void iterate(InputStream is, ZipEntryCallback action, Charset charset) {
    try {
        ZipInputStream in = null;
        if (charset == null) {
            in = new ZipInputStream(new BufferedInputStream(is));
        } else {
            in = ZipFileUtil.createZipInputStream(is, charset);
        }

        ZipEntry entry;
        while((entry = in.getNextEntry()) != null) {
            try {
                action.process(in, entry);
            } catch (IOException var6) {
                throw new ZipException("Failed to process zip entry '" + entry.getName() + " with action " + action, var6);
            } catch (ZipBreakException var7) {
                break;
            }
        }

    } catch (IOException var8) {
        throw ZipExceptionUtil.rethrow(var8);
    }
}

函数参数 InputStream isdoZipTransform 中的 inputStream , ZipEntryCallback actiondoZipTransform 中的 ZipEntryCallback 回调函数。程序通过while循环读取zip包中的目录和文件,回调执行 action.process(in, entry);

回到 UnZipTransformer:doZipTransform() 中对回调函数进行分析:

public void process(InputStream zipEntryInputStream, ZipEntry zipEntry) throws IOException {
    String zipEntryName = zipEntry.getName();
    long zipEntryTime = zipEntry.getTime();
    long zipEntryCompressedSize = zipEntry.getCompressedSize();
    String type = zipEntry.isDirectory() ? "directory" : "file";
    ....
    if (ZipResultType.FILE.equals(UnZipTransformer.this.zipResultType)) {
        File tempDir = new File(UnZipTransformer.this.workDirectory, message.getHeaders().getId().toString());
        tempDir.mkdirs();
        File destinationFile = new File(tempDir, zipEntryName);
        if (zipEntry.isDirectory()) {
            destinationFile.mkdirs();
        } else {
            SpringZipUtils.copy(zipEntryInputStream, destinationFile);
            uncompressedData.put(zipEntryName, destinationFile);
        }
    } ...
}

通过 String zipEntryName = zipEntry.getName(); 得到的结果如下:

spring-integration-zip-unsafe-unzip分析

当程序运行至 SpringZipUtils.copy(zipEntryInputStream, destinationFile); ,分析此时的参数状态。

spring-integration-zip-unsafe-unzip分析

此时, tempDir./here/0365902c-4673-075f-8767-24ec0d67c704/good.txt , zipEntryName../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../tmp/evil.txt ,导致通过 File destinationFile = new File(tempDir, zipEntryName); 得到的 destinationFile 的值是 ./here/0365902c-4673-075f-8767-24ec0d67c704/../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../tmp/evil.txt

最后执行 SpringZipUtils.copy(zipEntryInputStream, destinationFile); ,成功地在根目录tmp下写入 evil.txt

漏洞修复

在 Disallow traversal entity in zip 中的修复方案是:

tempDir.mkdirs(); //NOSONAR false positive
final File destinationFile = new File(tempDir, zipEntryName);
if (zipEntryName.contains("..") && !destinationFile.getCanonicalPath().startsWith(workDirectory.getCanonicalPath())) {
    throw new ZipException("The file " + zipEntryName + " is trying to leave the target output directory of " + workDirectory);
}

if (zipEntry.isDirectory()) {
    destinationFile.mkdirs(); //NOSONAR false positive
}

在回调函数中,增加了对 zipEntryNamedestinationFile 的判断。如果在 zipEntryName 含有 .. 并且通过 destinationFile.getCanonicalPath() 得到 destinationFile 的标准化路径,在本例中 destinationFile 最终的标准化路径是 C:/tmp/evil.txtworkDirectory 的标准化目录不一致,则认为是目录穿越的漏洞。

CVE-2018-1263

漏洞信息

spring-integration-zip-unsafe-unzip分析

其中红色部分说明的是,虽然1261的补丁能够很好地防御目录穿越写文件的漏洞,但是这个补丁仅仅只能防御框架本身,如果有用户自己使用了 destinationFile 并且没有采用补丁的方式进行校验,那么同样会存在目录穿越漏洞。所以这个漏洞的本质原因还是在于生成 destinationFile 的方式存在问题。

修复commit

UnZipTransformer.java 对生成的 destinationFile 进行校验。如下:

spring-integration-zip-unsafe-unzip分析

采用了 checkPath(message, zipEntryName) 的方式生成 destinationFile

spring-integration-zip-unsafe-unzip分析

在生成 destinationFile 进行判断,如果确认没有问题返回 destinationFile ,否则认为是目录穿越的漏洞。通过这种方式就能够保证生成的 destinationFile 是不存在目录穿越的问题的。

原文  http://blog.spoock.com/2018/05/16/cve-2018-1261/
正文到此结束
Loading...