扫码领资料
获网安教程
免费&进群
Zip Slip的漏洞成因非常简单,这个漏洞绑定的业务功能点:上传压缩包文件,后端解压压缩包保存其中的文件到服务器本地。
漏洞成因:待上传的压缩包中可以构造条目名,后端保存文件的时候,常常将条目名提取出来并和保存目录拼接作为最后的保存文件路径,但是压缩包是可控的,从而其中保存的原始条目名也是可控的,因此可以在文件名处利用../
跳转到任意目录,从而向任意目录写入新文件或者覆盖旧文件。具体案例可见下文。
在Zip Slip公布者文章中,提到,Java中的Zip Slip漏洞尤其普遍:
The vulnerability has been found in multiple ecosystems, including JavaScript, Ruby, .NET and Go, but is especially prevalent in Java, where there is no central library offering high level processing of archive (e.g. zip) files. The lack of such a library led to vulnerable code snippets being hand crafted and shared among developer communities such as StackOverflow.
本文从原生的Java.util.zip->zt-zip->spring integration zip进行Zip Slip漏洞分析,并在最后附上此漏洞的代审案例。
import zipfileif __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中,会取出../password
,并与保存目录拼接,其中获取../password
的java方法类似与zipEntry.getName()
。
漏洞代码:实际场景下的的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()
的值是可控的,从而造成路径穿越,最终写入任意文件。
引入依赖:
<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参数返回并没有任何的过滤。
因此,再看org.zeroturnaround.zip.ZipUtil.Unpacker#process
方法对zipEntry.getName()
没有任何的过滤。所以导致了Zip Slip漏洞的产生。
再回来看看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)
可以看到调用了原生的java.util.zip.ZipFile#ZipFile(java.io.File)
等API
此方法中也没有任何的过滤,直接将zip流内容和ZipEntry传入了org.zeroturnaround.zip.ZipUtil.Unpacker#process
(Unpacker#process
在上文已讲过)。
在zt-zip在1.13版本中进行了修复:https://github.com/zeroturnaround/zt-zip/commit/759b72f33bc8f4d69f84f09fcb7f010ad45d6fff#
引入依赖
<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
方法中存在如下代码片段
其中entry.getName()
的值是可控的,通过../
可以将恶意jsp文件写到web根目录。
寻找触发点,发现在com.DocSystem.controller.ManageController#upgradeSystem
方法中触发了com.DocSystem.controller.BaseController#unZip
方法
关键代码如下:
具体漏洞复现可参考:https://gitee.com/RainyGao/DocSys/issues/I65IYU
来源:https://xz.aliyun.com/t/12081
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权!
(hack视频资料及工具)
(部分展示)
往期推荐
【精选】SRC快速入门+上分小秘籍+实战指南
爬取免费代理,拥有自己的代理池
漏洞挖掘|密码找回中的套路
渗透测试岗位面试题(重点:渗透思路)
漏洞挖掘 | 通用型漏洞挖掘思路技巧
干货|列了几种均能过安全狗的方法!
一名大学生的黑客成长史到入狱的自述
攻防演练|红队手段之将蓝队逼到关站!
巧用FOFA挖到你的第一个漏洞