前言
Zip Slip的漏洞成因非常简单,这个漏洞绑定的业务功能点:上传压缩包文件,后端解压压缩包保存其中的文件到服务器本地。
漏洞成因:待上传的压缩包中可以构造条目名,后端保存文件的时候,常常将条目名提取出来并和保存目录拼接作为最后的保存文件路径,但是压缩包是可控的,从而其中保存的原始条目名也是可控的,因此可以在文件名处利用跳转到任意目录,从而向任意目录写入新文件或者覆盖旧文件。具体案例可见下文。../
在Zip Slip公布者文章中,提到,Java中的Zip Slip漏洞尤其普遍:
该漏洞已在多个生态系统中发现,包括JavaScript,Ruby,.NET和Go,但在Java中尤其普遍,因为Java没有中央库提供存档(例如.zip)文件的高级处理。缺乏这样的库导致易受攻击的代码片段被手工制作并在StackOverflow等开发人员社区之间共享。
本文从原生的Java.util.zip->zt-zip->spring integration zip进行Zip Slip漏洞分析,并在最后附上此漏洞的代审案例。
生成恶意zip
import zipfile
if __name__ == "__main__":
try:
zipFile = zipfile.ZipFile("poc.zip", "a", zipfile.ZIP_DEFLATED) ##生成的zip文件
info = zipfile.ZipInfo("poc.zip")
zipFile.write("D:/tgao/pass/1", "../password", zipfile.ZIP_DEFLATED) ##压缩的文件和在zip中显示的文件名
zipFile.close()
except IOError as e:
raise e
上述生成的恶意zip,在Zip Slip中,会取出,并与保存目录拼接,其中获取的java方法类似与。../password
../password
zipEntry.getName()
原生的Java.util.zip
漏洞代码:实际场景下的的zip包是可控的,如通过文件上传等功能
package 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 {
//解压zip的包
String fileAddress = "D:/pythonProject/exp/ctf/poc.zip";
//zip文件解压路径
String unZipAddress = "D:/tgao/pass/";
//去目录下寻找文件
File file = new File(fileAddress);
ZipFile zipFile = null;
try {
zipFile = new ZipFile(file);//设置编码格式
} catch (IOException exception) {
exception.printStackTrace();
System.out.println("解压文件不存在!");
}
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();
}
if (zipFile != null) {
zipFile.close();
}
}
}
漏洞成因: 中的值是可控的,从而造成路径穿越,最终写入任意文件。File f = new File(unZipAddress + zipEntry.getName());
zipEntry.getName()
ZT-拉链
引入依赖:
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-zip</artifactId>
<version>1.12</version>xml
</dependency>
zt-zip组件中的解压功能,是在原生的java.util.zip基础上进行的封装。
漏洞代码:实际场景下的的zip包是可控的,如通过文件上传等功能。
package zip;
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);
}
}
跟进org.zeroturnaround.zip.ZipUtil#unpack(java.io.File, java.io.File)
继续跟进org.zeroturnaround.zip.ZipUtil#unpack(java.io.File, java.io.File, org.zeroturnaround.zip.NameMapper)
在上述方法中使用创建对象
可以先看其中的方法new ZipUtil.Unpacker(outputDir, mapper)
ZipEntryCallback
(ZipUtil.Unpacker)
org.zeroturnaround.zip.ZipUtil.Unpacker#process
上述代码中的在调用方法中传入的this.mapper
org.zeroturnaround.zip.ZipUtil#unpack(java.io.File, java.io.File)
进入org.zeroturnaround.zip.IdentityNameMapper
上述的map方法直接将传入的name参数返回并没有任何的过滤。
因此,再看方法对没有任何的过滤。所以导致了Zip Slip漏洞的产生。org.zeroturnaround.zip.ZipUtil.Unpacker#process
zipEntry.getName()
再回来看看org.zeroturnaround.zip.ZipUtil#unpack(java.io.File, java.io.File, org.zeroturnaround.zip.NameMapper)
跟进org.zeroturnaround.zip.ZipUtil#iterate(java.io.File, org.zeroturnaround.zip.ZipEntryCallback)
继续跟进org.zeroturnaround.zip.ZipUtil#iterate(java.io.File, org.zeroturnaround.zip.ZipEntryCallback, java.nio.charset.Charset)
可以看到调用了原生的等API
此方法中也没有任何的过滤,直接将zip流内容和ZipEntry传入了(在上文已讲过)。java.util.zip.ZipFile#ZipFile(java.io.File)
org.zeroturnaround.zip.ZipUtil.Unpacker#process
Unpacker#process
在zt-zip在1.13版本中进行了修复:https://github.com/zeroturnaround/zt-zip/commit/759b72f33bc8f4d69f84f09fcb7f010ad45d6fff#
弹簧集成拉链
CVE-2018-1261
引入依赖
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-zip</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
<type>jar</type>
</dependency>
spring-integration-zip
依赖于zt-zip
漏洞代码:实际场景下的的zip包是可控的,如通过文件上传等功能
package zip;
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);
}
}
}
跟进构造方法org.springframework.integration.zip.transformer.UnZipTransformer#UnZipTransformer
跟进构造方法org.springframework.integration.zip.transformer.AbstractZipTransformer#AbstractZipTransformer
初始化了和属性,前者为,后续会用到此值,而默认值为后续也会使用到该值。在我的测试环境下,如下:zipResultType
workDirectory
ZipResultType.FILE
workDirectory
new File(System.getProperty("java.io.tmpdir") + File.separator + "ziptransformer")
System.getProperty("java.io.tmpdir") + File.separator + "ziptransformer"
创建完后,执行方法UnZipTransformer
org.springframework.integration.transformer.AbstractTransformer#transform
其中参数值是文件读取流,继续跟进message
zip
org.springframework.integration.zip.transformer.AbstractZipTransformer#doTransform
继续跟进org.springframework.integration.zip.transformer.UnZipTransformer#doZipTransform
继续跟进
调用了的,只不过在这里自己创建了一个匿名对象,最后会调用此匿名对象的方法zt-zip
api
spring integration zip
ZipEntryCallback
process
没有任何过滤,导致zip slip发生。
修复方案如下:https://github.com/spring-projects/spring-integration-extensions/commit/a5573eb232ff85199ff9bb28993df715d9a19a25
审计实战
项目地址:https://gitee.com/RainyGao/DocSys
在方法中存在如下代码片段com.DocSystem.controller.BaseController#unZip
其中的值是可控的,通过可以将恶意jsp文件写到web根目录。
寻找触发点,发现在方法中触发了方法entry.getName()
../
com.DocSystem.controller.ManageController#upgradeSystem
com.DocSystem.controller.BaseController#unZip
关键代码如下: 具体漏洞复现可参考:
https://gitee.com/RainyGao/DocSys/issues/I65IYU
来源:先知社区的【TGAO】师傅
注:如有侵权请联系删除
如需进群进行技术交流,请扫该二维码
原文始发于微信公众号(衡阳信安):Java Zip Slip漏洞案例分析及实战挖掘
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论