Restlet XMLDecoder 远程代码执行漏洞分析(CVE-2013-4221)

Posted raul17的安全思考空间

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Restlet XMLDecoder 远程代码执行漏洞分析(CVE-2013-4221)相关的知识,希望对你有一定的参考价值。

本文编辑于:2018-05-03
原文:https://zhuanlan.zhihu.com/p/36002604

0x00 写在前面

最近在整理Java Web组件相关的命令执行漏洞的时候,看到个2013年Restlet的一个CVE漏洞,网上没看到相关的漏洞分析。该漏洞是XMLDecoder反序列化漏洞,跟Weblogic CVE-2017-10271的漏洞原理 ()类似,故准备简单分析一下。

Restlet 2.1.2版本用XMLDecoder类反序列化用户控制的XML数据和二进制数据,这可使远程攻击者通过提交特制的XML数据或二进制数据,利用此漏洞执行任意Java代码,或调用Java对象上的任意反序列化方法。

51CTO上有个XMLDecoder反序列化漏洞的demo:

http://blog.51cto.com/duallay/1961598

关键代码为:

 java.io.File file = new java.io.File("d:/tmp/xmldecoder.xml");
  java.beans.XMLDecoder xd = null;
  try {
      xd = new java.beans.XMLDecoder(new BufferedInputStream(new FileInputStream(file)));
  } catch (FileNotFoundException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
  }
  Object s2 = xd.readObject();
  xd.close();

相同的漏洞原理,信任用户输入,在调用XMLDecoder解析XML文件的时候,存在命令执行漏洞。

0x01 漏洞环境

https://github.com/o2platform/DefCon_RESTing/tree/master/Demos/RestletXMLDecoder

Linux上进入代码目录直接运行如下命令做Idea远程调试相关的配置,进行远程调试。

java -cp ./lib/org.restlet.jar:./lib/groovy-all-1.8.9.jar:. ServerRestlets

Windows将代码导入Idea,直接debug运行ServerRestlets,本地开启8182端口,即可进行调试。

0x02 漏洞分析

参见链接(https://github.com/restlet/restlet-framework-java/commit/ae3ef64e28e877282822b79f894deedc2f9f8c06)版本更新中的更新日志。针对该漏洞的修复方案:作者将框架提供的默认转换器内的ObjectRepresentation删除了对XML序列化的JavaBean的默认支持。并在ObjectRepresentation类中更新了相关的注释,添加了相关的安全警告。

根据测试demo,简单分析执行过程。

当运行ServerRestlets类,代码将Restlet服务当作单独的Java程序进行部署,启动8182端口。在createInboundRoot()方法中绑定资源路径到对应的处理资源类CustomerRessource(当对/customer发起请求时,处理CustomerRessource类)。

CustomerRessource类中明确了如何对Get和Put请求进行处理。

我们看Put请求时的处理过程,相关代码为:

@Put
public void storeItem(Representation entity) throws IOException 
 // GroovyShell
 GroovyShell shell = new GroovyShell();
 Object value = shell.evaluate("println 'Hello World!';");
 ObjectRepresentation<Customer> repObject;
 try {
  //System.out.println(entity.getStream());
  repObject = new ObjectRepresentation<Customer>(entity);
  Customer customer;
  customer = repObject.getObject();
  System.out.println("name : " + customer.name);
  System.out.println("firstname : " + customer.firstName);
  setStatus(Status.SUCCESS_OK);
 } catch (IllegalArgumentException e) { 
  e.printStackTrace();
 } catch (ClassNotFoundException e) { 
  e.printStackTrace();
 } 
}

我们发送poc时,其中的报文body被Representation类进行了封装。可以通过entity.getText()可以获取到当前的body内容。

当我们打印entity.getText()的内容的时候:System.out.println(entity.getStream());

控制台可看到我们发送的xml报文:

PS: 但是注意,getText()方法不能重复执行,否则会报错,通过查源码得知:在http响应时Representation封装了一个io流,只能读取一次。

封装了报文body的Representation类传入到ObjectRepresentation类。查看该类的构造方法,代码如下:

public ObjectRepresentation(Representation serializedRepresentation) throws IOException, ClassNotFoundException, IllegalArgumentException {
     super(MediaType.APPLICATION_JAVA_OBJECT);
     InputStream is;
     if(serializedRepresentation.getMediaType().equals(MediaType.APPLICATION_JAVA_OBJECT)) {
         this.setMediaType(MediaType.APPLICATION_JAVA_OBJECT);
         is = serializedRepresentation.getStream();
         ObjectInputStream decoder = new ObjectInputStream(is);
         this.object = (Serializable)decoder.readObject();
         if(is.read() != -1) {
             throw new IOException("The input stream has not been fully read.");
         }
         decoder.close();
     } else {
         if(!serializedRepresentation.getMediaType().equals(MediaType.APPLICATION_JAVA_OBJECT_XML)) {
             throw new IllegalArgumentException("The serialized representation must have this media type: " + MediaType.APPLICATION_JAVA_OBJECT.toString() + " or this one: " + MediaType.APPLICATION_JAVA_OBJECT_XML.toString());
         }
         this.setMediaType(MediaType.APPLICATION_JAVA_OBJECT_XML);
         is = serializedRepresentation.getStream();
         XMLDecoder decoder1 = new XMLDecoder(is);
         this.object = (Serializable)decoder1.readObject();
         if(is.read() != -1) {
             throw new IOException("The input stream has not been fully read.");
         }
         decoder1.close();
     }
 }

该构造方法会先判断我们Put请求中的Content-Type是application/x-java-serialized-object,还是application/x-java-serialized-object+xml

查看MediaType中的定义,代码如下:

public static final MediaType APPLICATION_JAVA_OBJECT = register("application/x-java-serialized-object""Java serialized object");
public static final MediaType APPLICATION_JAVA_OBJECT_GWT = register("application/x-java-serialized-object+gwt""Java serialized object (using GWT-RPC encoder)");
public static final MediaType APPLICATION_JAVA_OBJECT_XML = register("application/x-java-serialized-object+xml""Java serialized object (using JavaBeans XML encoder)");

当请求中的Content-Type为application/x-java-serialized-object时,直接使用ObjectInputStream类序列化对象;当Content-Type为application/x-java-serialized-object+xml时,使用XMLEncoder序列化对象。

故我们的poc中,需将Context-Type修改为application/x-java-serialized-object+xml

回到ObjectRepresentation类的构造方法,当我们请求中的Content-Type为application/x-java-serialized-object+xml时,执行else{}中的代码,关键代码为:

else {
        ......
        this.setMediaType(MediaType.APPLICATION_JAVA_OBJECT_XML);
        is = serializedRepresentation.getStream();
        XMLDecoder decoder1 = new XMLDecoder(is);
        this.object = (Serializable)decoder1.readObject();
        if(is.read() != -1) {
            throw new IOException("The input stream has not been fully read.");
        }
        decoder1.close();
    }

通过getStream()取出Representation类中封装的流,通过XMLDecoder类进行反序列化操作。执行readObject()方法,导致反序列化远程代码执行。

0x03 后记

整个漏洞分析过程还是比较简单吧。就是研究Restlet花了挺长时间。爬了挺多坑,但是对XMLDecoder反序列化和Restlet的项目有了更深的了解。

0x04 参考链接

  1. http://blog.diniscruz.com/2013/08/using-xmldecoder-to-execute-server-side.html
  2. https://github.com/restlet/restlet-framework-java/issues/774
  3. https://github.com/o2platform/DefCon_RESTing/tree/master/Demos/RestletXMLDecoder

以上是关于Restlet XMLDecoder 远程代码执行漏洞分析(CVE-2013-4221)的主要内容,如果未能解决你的问题,请参考以下文章

Weblogic XMLDecoder反序列化漏洞(CVE-2017-10271)

weblogic远程调试XMLDecoder RCE

XMLDecoder反序列化漏洞检测思考

XMLDecoder反序列化漏洞

CVE-2017-10271 XMLDecoder 反序列化

JAVA漏洞重现:XMLDecoder反序列化漏洞