近日Snyk的研究员公布了一个名为Zip Slip的漏洞。攻击者可以恶意构造Zip压缩文件,通过路径遍历覆盖任意文件,从而导致潜在的代码执行。Zip Slip是“任意文件覆盖”和“目录遍历”问题的结合,攻击者可以将文件解压缩到正常解压缩路径之外并覆盖敏感文件,从而等待系统或用户调用它们来实现代码执行。
详细信息可参考:
https://snyk.io/research/zip-slip-vulnerability
相关阅读:
漏洞影响范围
Zip Slip是一个广泛存在的任意文件覆盖漏洞,通常会导致远程命令执行。该漏洞影响范围极大:
受影响的产品:惠普、Amazon、apache、Pivotal等
受影响的编程语言:JavaScript、Python、Ruby、.NET、Go、Groovy等
受影响的压缩文件类型:tar、jar、war、cpio、apk、rar、7z等
具体受影响的产品详见附录A。
影响排查
使用存在Zip Slip漏洞的库,且项目没有进行目录遍历验证,直接包含易受攻击代码都将受此漏洞影响。
当系统同时满足以下两个条件时,可以判断其在受影响范围之内:
系统中使用的组件在受影响范围内。
项目中存在可被此漏洞利用的代码。
因此,用户需同时检查所使用组件版本及解压缩功能的代码实现情况。
版本检查
snyk正在维护一个GitHub项目,用于列出所有已发现受Zip Slip影响的项目,及其修复情况、补丁版本。建议相关企业关注绿盟科技安全预警通告,或直接查看GitHub项目(https://github.com/snyk/zip-slip-vulnerability),根据自身情况,对自有资产进行版本排查。
目前公开的受影响产品信息请参考附录A。若系统中使用了受影响的库或项目,且版本在受影响范围内,判断可能存在受此漏洞影响的风险。
代码检查
Zip Slip主要是由于代码中没有对压缩包中的文件名进行合法性校验,直接将文件名拼接到待解压目录中,导致存在路径遍历风险。因此,可通过对代码进行审计,来判断是否受此漏洞影响。
以Java为例(易受攻击代码示例):
Enumeration<ZipEntry> entries = zip.getEntries(); while (entries.hasMoreElements()) { ZipEntry e = entries.nextElement(); File f = new File(destinationDir, e.getName()); InputStream input = zip.getInputStream(e); IOUtils.copy(input, write(f)); }
红字部分的e.getName()直接与待解压缩路径拼接,且未进行路径遍历验证。若项目中存在类似上述代码,可判断此项目将受Zip Slip漏洞影响,需尽快修复。
漏洞利用验证
利用Zip Slip漏洞,将包含有恶意脚本的文件,解压缩至动态脚本可解析的目录,攻击者即可获取服务器控制权限,以下代码可生成用于漏洞验证的zip文件。
File evil_file = new File(FILEPATH); File zip_file = new File(ZIPPATH); ZipOoutputStream out = new ZipOutputStream(new FIleOutputStream(zip_file)); String prefix = “.. /1.jsp” try { BufferedInpotStream bis = new BufferedInputStream(new FileInputStream(evil_file)); ZipEntry entry = new ZipEntry(BASEDIR + prefix + evil_file.getName()); out.putnextEntry(entry); int count; byte data[] = new byte[BUFFER]; while((count = bis.read(data, 0, BUFFER)) != -1) { out.write(data, 0, count); } bis.close(); } catch(Exception e) { Throw new Exception(e); }
以zt-zip1.12版本为例,对构造的压缩文件进行解压,示例代码如下图所示:
传入构造的zip文件,解压后如果1.jsp文件跳入上级目录,则说明漏洞存在。利用此漏洞,可造成页面篡改,命令文件覆盖等危害,请受影响用户尽快修复。
在使用官方已经修复的zt-zip 1.13版本对恶意构造压缩文件进行解压时,会返回异常。此版本不受Zip Slip影响。
漏洞防护
版本升级
用户可根据https://github.com/snyk/zip-slip-vulnerability中给出的修复信息自行进行版本更新,达到修复此漏洞的目的。部分厂商暂未发布补丁版本,请用户持续关注,即使进行补丁更新。下列为部分产品的补丁版本情况:
厂商 | 产品 | 版本 | 修复版本链接 |
Pivotal | spring-integration-zip | 1.0.1 | https://github.com/spring-projects/spring-integration-extensions/archive/zip.v1.0.1.RELEASE.zip |
Pivotal | spring-integration-zip | 1.0.2 | https://github.com/spring-projects/spring-integration-extensions/archive/zip.v1.0.2.RELEASE.zip |
Lucee | Lucee | 5.2.7
5.2.8.47 |
http://cdn.lucee.org/lucee-express-5.2.7.62.zip |
厂商 | 产品 | 编程语言 | 受影响版本 | 修复版本下载链接 |
npm library | unzipper | JavaScript | <0.8.13 | https://github.com/ZJONSSON/node-unzipper/archive/master.zip |
npm library | adm-zip | JavaScript | <0.4.9 | https://github.com/cthackers/adm-zip/archive/master.zip |
Java library | codehaus/plexus-archiver | Java | <3.6.0 | https://github.com/codehaus-plexus/plexus-archiver/archive/master.zip |
Java library | zeroturnaround/zt-zip | Java | <1.13 | https://github.com/zeroturnaround/zt-zip/archive/master.zip |
.NET library | DotNetZip.Semverd | .NET | <1.11.0 | https://github.com/haf/DotNetZip.Semverd/archive/master.zip |
.NET library | SharpCompress | .NET | <0.21.0 | https://github.com/adamhathcock/sharpcompress/archive/master.zip |
.NET library | mholt/archiver | Go | 所有版本 | 未发布新版本,官方提供了修复代码:
https://github.com/mholt/archiver/commit/e4ef56d48eb029648b0e895bb0b6a393ef0829c3 |
Apache | commons-compress | Java | <1.17 | https://github.com/apache/commons-compress/archive/master.zip |
代码修复
当应用系统实现解压缩功能时,以Java语言为例,可使用如下代码对此漏洞进行防护:
String canonicalDestinationDirPath = destinationDir.getCanonicalPath(); File destinationfile = new File(destinationDir, e.getName()); String canonicalDestinationFile = destinationfile.getCanonicalPath(); if (!canonicalDestinationFile.startsWith(canonicalDestinationDirPath)) { throw new ArchiverException("Entry is outside of the target dir: " + e.getName()); }
其他常用语言的漏洞代码及修复建议请参考附录B内容。
漏洞简析
Zip Slip是通过在解压压缩文件时造成目录遍历而被利用。利用前提是攻击者可以访问目标文件夹之外的文件系统的一部分,然后攻击者可以覆盖可执行文件,远程调用它们,或等待系统/用户调用它们,实现远程命令执行。该漏洞还可以通过覆盖配置文件或其他敏感资源对受害者造成损害。在客户端(用户)计算机和服务器上都可利用此漏洞进行攻击。
利用此漏洞需要不执行验证检查的恶意压缩文件和解压代码两个部分。首先,zip文件的内容在解压时需要有一个或多个脱离目标目录的文件。在下面的例子中,我们可以看到zip文件中有两个文件,一个good.txt文件将被提取到目标目录中,另一个 evil.jsp 文件尝试遍历目录树以打开根目录,然后将文件添加到temp目录中。当在根目录中尝试cd..时,发现仍处于根目录中,因此恶意路径可能包含多个级别的目录 ../ 。
good.txt ../../../../../../../../WEB-INF/evil.jsp
接着需要使用自己的代码或库来解压文件。解压缩代码忽略压缩文件路径的验证时存在此漏洞。下面是一个易受攻击的代码片段示例(以Java为例)。
Enumeration<ZipEntry> entries = zip.getEntries(); while (entries.hasMoreElements()) { ZipEntry e = entries.nextElement(); File f = new File(destinationDir, e.getName()); InputStream input = zip.getInputStream(e); IOUtils.copy(input, write(f)); }
红字部分的 e.getName()与目标目录连接,未进行验证。此时,当zip压缩文件到达evil.txt时,它将添加zip的完整路径(包括 ../)到目标目录中,从而使evil.txt被写入目标目录之外。
附录A 漏洞影响情况
各厂商的修复进度仍在持续中,请用户及时关注漏洞修复的情况。
参考链接:
https://github.com/snyk/zip-slip-vulnerability
受影响的库
厂商 | 产品 | 编程语言 | 受影响版本 | 修复版本 |
npm library | unzipper | JavaScript | <0.8.13 | 0.8.13 |
npm library | adm-zip | JavaScript | <0.4.9 | 0.4.9 |
Java library | codehaus/plexus-archiver | Java | <3.6.0 | 3.6.0 |
Java library | zeroturnaround/zt-zip | Java | <1.13 | 1.13 |
Java library | zip4j | Java | 所有版本 | 暂未修复 |
.NET library | DotNetZip.Semverd | .NET | <1.11.0 | 1.11.0 |
.NET library | SharpCompress | .NET | <0.21.0 | 0.21.0 |
.NET library | mholt/archiver | Go | 所有版本 | 在项目源码中提供了补丁代码:https://github.com/mholt/archiver/commit/e4ef56d48eb029648b0e895bb0b6a393ef0829c3 |
Oracle | java.util.zip | Java | 没有高危API | 在项目源码修复 |
Apache | commons-compress | Java | 没有高危API | 在项目源码修复 |
.NET library | SharpZipLib | .NET | 没有高危API | 在项目源码修复 |
Ruby gem | zip-ruby | Ruby | 没有高危API | 在项目源码修复 |
Ruby gem | rubyzip | Ruby | 没有高危API | 在项目源码修复 |
Ruby gem | zipruby | Ruby | 没有高危API | 在项目源码修复 |
Go library | archive | Go | 没有高危API | 在项目源码修复
|
受影响的项目
厂商 | 产品 | 受影响版本 | 修复版本 |
Apache Storm | Storm | <1.0.7 | 1.0.7 |
Apache Hadoop | Hadoop | 暂未公开 | 暂未公开 |
Apache Hive | Hive | 暂未公开 | 暂未公开 |
Apache | Maven | 暂未公开 | 暂未公开 |
Apache | Ant | <1.9.12 | 1.9.12 |
Pivotal | spring-integration-zip | <1.0.1 | 1.0.1 |
Pivotal | spring-integration-zip | <1.0.2 | 1.0.2 |
HP | Fortify Cloud Scan Jenkins Plugin | 暂未公开 | 暂未公开 |
OWASP | DependencyCheck | 暂未公开 | 暂未公开 |
Amazon | AWS Toolkit for Eclipse | 暂未公开 | 暂未公开 |
SonarCube | SonarCube | 暂未公开 | 暂未公开 |
Cinchapi | Concourse | 暂未公开 | 暂未公开 |
Orient Technologies | OrientDB | 暂未公开 | 暂未公开 |
FenixEdu | Academic | 暂未公开 | 暂未公开 |
Lucee | Lucee | <5.2.7
<5.2.8.47 |
5.2.7, 5.2.8.47 |
未受影响且已移除漏洞代码的项目
已验证产品不受此漏洞影响,为保证安全性,官方移除并修复了项目中的漏洞代码。
厂商 | 产品 | 漏洞代码移除时间 |
Apache | Kylin | 24/4/2018 |
Apache | NiFi | 24/4/2018 |
Apache | Geode | 20/4/2018 |
Jenkins | Jenkins CI | 5/5/2018 |
Elastic | ElasticSearch | 10/5/2018 |
Pinot | 22/5/2018 | |
AnkiDroid | Anki-Droid | 31/5/2018 |
ata4 | bspsrc | 30/5/2018 |
eirslett | frontend-maven-plugin | 30/5/2018 |
存在漏洞代码但被认为无法利用(漏洞可能存在)
官方称产品不受此漏洞影响,且未对漏洞代码进行修复的产品。
厂商 | 产品 |
JetBrains | Intellij-community |
Apache | Apex |
Apache | Zeppelin |
Apache | Reef |
Apache | BookKeeper |
Apache | Pulsar |
Apache | Heron |
Apache | Gobblin |
Apache | Gobblin |
Apache | SystemML |
Gradle | Gradle |
Gradle | Gradle |
Gradle | Gradle |
plasma-umass | doppio |
streamsets | DataCollector |
附录B 各语言漏洞代码及修复方案
Groovy
和Java一样,Groovy在不同项目的代码库中也存在漏洞代码。示例如下:
final zipInput = new ZipInputStream(newFileInputStream(self)) zipInput.withStream { def entry while(entry = zipInput.nextEntry) { final file = new File(dest, entry.name) file.parentFile?.mkdirs() def output = new FileOutputStream(file) output.withStream { output << zipInput } unzippedFiles << file } }
漏洞修复代码(对路径遍历进行验证):
final canonicalDestinationDirPath = destinationDir.getCanonicalPath() final destinationfile = new File(destinationDir, e.name) final canonicalDestinationFile = destinationfile.getCanonicalPath() if (!canonicalDestinationFile.startsWith(canonicalDestinationDirPath + File.separator)) { throw new ArchiverException("Entry is outside of the target dir: ${e.name}") }
JavaScript
JavaScript拥有更多支持从归档中提取的功能的官方库,且在公开之前修复了易受攻击的库。漏洞代码示例如下(join命令将两个路径参数连接起来,并在解析后返回最短的路径):
self.on('entry', function(entry) { entry.pipe(Writer({ path: path.join(opts.path,entry.path) }))
验证代码:
var filePath = path.join(targetFolder, entry.path); if (filePath.indexOf(path.join(targetFolder, path.sep)) != 0) { return; }
.NET
.Net也有支持压缩包提取功能的官方库。且核心.NET库中的代码非常整洁。漏洞代码示例如下:
public static void WriteToDirectory(IArchiveEntry entry, string destDirectory,ExtractionOptions options){ string file = Path.GetFileName(entry.Key); string destFileName = Path.Combine(destDirectory, file); entry.WriteToFile(destFileName, options); }
验证代码示例如下:
destFileName = Path.GetFullPath(Path.Combine(destDirecory, entry.Key)); string fullDestDirPath = Path.GetFullPath(destDirectory); if (!destFileName.StartsWith(fullDestDirPath)) { throw new ExtractionException("Entry is outside of the target dir: " + destFileName); }
Go
Go只有一个存在漏洞的库,已在公开后两天内完成修复。
Join命令连接两个路径参数,并在解析后返回最短路径。漏洞代码示例如下:
func (rarFormat) Read(input io.Reader, dest string) { rr := rardecode.NewReader(input, "") for { header := rr.Next() writeNewFile(filepath.Join(dest, header.Name), rr, header.Mode()) } }
验证代码如下:
func sanitizeExtractPath(filePath string, destination string) error { destpath := filepath.Join(destination, filePath) if !strings.HasPrefix(destpath, destination) { return fmt.Errorf("%s: illegal file path", filePath) } return nil }