zip slip文件
一、简述
压缩有个 zip slip问题。简单来说现在有这两个目录:
├── test
│ └── evil.zip //bad virus
└── test1
└── demo.txt //hello
- test目录下有我们想解压缩 evil.zip文件。 test1目录是我们不想对外暴露的个人文件夹。
- evil.zip里是一个 原名为"…/test1/demo.txt"的文件。压缩后文件名并没有丢失(可以看成是存到zip{}这个结构体里了),解压缩的时候就会获取原始文件名"…/test1/demo.txt",然后解压的时候,就会覆盖test1文件夹下的demo.txt,把原文件内容“hello”变成“bad virus”。就篡改了用户的文件。
- 试想一下,test1文件下是一个可执行文件呢? 这样的文件被替换后,当执行这个可执行文件时,电脑就可能会中病毒。
- 注:并不能替换根目录/或者/etc目录下的文件,因为会爆Permisson Deny错误。
解决这个问题的办法是,不信任原始的文件名,对其进行校验。 并且校验他的父目录是不是test即可。
怎么解决这个问题呢? 下面是go和java代码。
if (!fileName.contains("..") && !f.getCanonicalPath().startsWith(destFile.getCanonicalPath())) {}
if !strings.HasPrefix(fileName, zipFileBase) {
return errors.New("invalid file path")
}
二、zip slip问题 详述
2.1 问题介绍
ZipSlip攻击是一种利用压缩文件中的目录遍历漏洞来覆盖系统中的任意文件的攻击方式,通常会导致远程命令执行。攻击者通过构造一个压缩文件条目中带有…/的压缩文件,上传后交给应用程序进行解压,由于程序解压时没有对文件名进行合法性的校验,而是直接将文件名拼接在待解压目录后面,导致可以将文件解压到正常解压缩路径之外并覆盖可执行文件、配置文件或其他敏感文件。ZipSlip攻击影响多种编程语言和压缩文件格式,例如tar、jar、war、cpio、apk、rar、7z等。为了防止ZipSlip攻击,应用程序在解压压缩文件时应该对文件路径进行验证,避免目录遍历。
2.1代码示例
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.zip.ZipOutputStream;
public class Zip {
public static void main(String[] args) throws IOException {
String srcName = "/Users/Mac/Desktop/zip_slip/test/test.txt";
//第一个参数是需要压缩的源路径;第二个参数是压缩文件的目的路径,这边需要将压缩的文件名字加上去
compress(srcName, "/Users/Mac/Desktop/zip_slip/test/evil.zip");
}
/**
* 压缩文件
*
* @param srcFilePath 压缩源路径
* @param destFilePath 压缩目的路径
*/
public static void compress(String srcFilePath, String destFilePath) {
File src = new File(srcFilePath);
if (!src.exists()) {
throw new RuntimeException(srcFilePath + "不存在");
}
File zipFile = new File(destFilePath);
try {
FileOutputStream fos = new FileOutputStream(zipFile);
ZipOutputStream zos = new ZipOutputStream(fos);
String baseDir = "../test/";
compressbyType(src, zos, baseDir);
zos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 按照原路径的类型就行压缩。文件路径直接把文件压缩,
*
* @param src
* @param zos
* @param baseDir
*/
private static void compressbyType(File src, ZipOutputStream zos, String baseDir) {
if (!src.exists())
return;
System.out.println("压缩路径" + baseDir + src.getName());
compressFile(src, zos, baseDir);
}
/**
* 压缩文件
*/
private static void compressFile(File file, ZipOutputStream zos, String baseDir) {
if (!file.exists())
return;
try {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
ZipEntry entry = new ZipEntry(baseDir + file.getName());
zos.putNextEntry(entry);
int count;
byte[] buf = new byte[1024];
while ((count = bis.read(buf)) != -1) {
zos.write(buf, 0, count);
}
bis.close();
} catch (Exception e) {
}
}
}
class Unzip {
public static void main(String[] args) throws IOException {
//解压zip的包
String fileAddress = "/Users/Mac/Desktop/zip_slip/test/evil.zip";
//zip文件解压路
String unZipAddress = "/Users/Mac/Desktop/zip_slip/test/";
//去目录下寻找文件
File file = new File(fileAddress);
ZipFile zipFile = null;
try {
zipFile = new ZipFile(file, "GBK");//设置编码格式
} catch (IOException exception) {
exception.printStackTrace();
System.out.println("解压文件不存在!");
}
Enumeration e = zipFile.getEntries();
while (e.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry) e.nextElement();
System.out.println(zipEntry.getName());
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();
}
}
}
只看UnZip类就可以。本来想解压到test文件夹下,结果因为文件名有坑,就导致解压到了test1文件下,并且把test1文件下本来存在的test.txt目录给覆盖了。
上面写Zip类是因为,系统不允许我们创建一个名为 "…/test1/test.txt"的文件,所以就用程序来创建并压缩。