Adobe ColdFusion:CVE-2017-3066漏洞的其他利用思路

 

一、前言

在最近一次渗透测试任务中,我的小伙伴Thomas遇到了几台服务器,这几台服务器上运行着Adobe ColdFusion 11以及12平台,其中某些服务器存在CVE-2017-3066漏洞,但无法通过TCP协议外连,因此无法利用这个漏洞。Thomas向我求助,问我是否有办法帮他获取SYSTEM权限的shell,因此我把我所做的工作汇成这篇文章与大家一起分享。

 

二、Adobe ColdFusion & AMF简介

在讨论技术细节之前,我先简单介绍一下Adobe ColdFusion(CF)。Adobe ColdFusion是类似ASP.net之类的应用程序开发平台(Application Development Platform),然而诞生时间要更为久远。开发者可以使用Adobe ColdFusion来搭建网站、SOAP以及REST Web服务,使用Action Message Format(AMF)与Adobe Flash进行交互。

AMF协议是一种自定义的二进制序列化协议。该协议有两种格式:AMF0以及AMF3。一个Action Message由头部(header)以及主体(body)所组成。AMF0以及AMF3中支持多种数据类型。比如,AMF3格式支持的协议元素以及类型标识符如下所示:

Undefined      - 0x00
Null           - 0x01
Boolean        - 0x02
Boolean        - 0x03
Integer        - 0x04
Double         - 0x05
String         - 0x06
XML            - 0x07
Date           - 0x08
Array          - 0x09
Object         - 0x0A
XML End        - 0x0B
ByteArray      - 0x0C

如果想了解AMF0以及AMF3二进制消息格式的详细内容,大家可以查阅相关维基百科页面。

不同语言中关于AMF的具体实现也有所不同。对于Java来说,我们可以使用Adobe BlazeDS(现在是Apache BlazeDS),Adobe ColdFusion中也用到了这个技术。

BlazeDS AMF序列化器(serializer)可以序列化复杂的对象图(object graph)。序列化器会从根对象(root object)开始处理,递归序列化根对象成员。

在序列化复杂对象方面,BlazeDS支持两种常用的序列化技术:

1、序列化Bean属性(AMF0以及AMF3);

2、使用Java的java.io.Externalizable接口来序列化(AMF3)。

序列化Bean属性

这种技术需要待序列化的对象具有公开的无参数构造函数,每个成员都拥有公开的Getter以及Setter方法(符合JavaBeans规范)。

为了收集某个对象所有的成员值,AMF序列化器会在序列化过程中调用所有的Getter方法。成员名、成员值以及对象的类名存放在Action消息的body区中。

在反序列化过程中,程序会从Action消息中获取类名,构造新的对象,然后以成员值作为参数调用每个成员名对应的set方法。这一个过程由专门的方法来实现,比如flex.messaging.io.amf.Amf3Input类中的readScriptObject()方法或者flex.messaging.io.amf.Amf0Input类中的readObjectValue()方法。

使用java.io.Externalizable接口序列化

如果某些类实现(implement)了java.io.Externalizable接口(继承自java.io.Serializable),BlazeDS还支持这些类的复杂对象的序列化。

public abstract interface Externalizable
  extends Serializable
{
  public abstract void writeExternal(ObjectOutput paramObjectOutput)
    throws IOException;

  public abstract void readExternal(ObjectInput paramObjectInput)
    throws IOException, ClassNotFoundException;
}

实现这一接口的每个类都需要自己提供反序列化逻辑,调用相关方法来处理java.io.ObjectInput对象,读取序列化后的类型及字符串(比如method read(byte[] paramArrayOfByte))。

在AMF3中对某个对象(类型标识符为0xa)进行反序列化时,会调用flex.messaging.io.amf.Amf3Input类的readScriptObject()方法。在如下代码的759行,readExternalizable方法会被调用,该方法会在待反序列化的对象上调用readExternal()方法。

/*      */   protected Object readScriptObject()
/*      */     throws ClassNotFoundException, IOException
/*      */   {
/*  736 */     int ref = readUInt29();
/*      */     
/*  738 */     if ((ref & 0x1) == 0) {
/*  739 */       return getObjectReference(ref >> 1);
/*      */     }
/*  741 */     TraitsInfo ti = readTraits(ref);
/*  742 */     String className = ti.getClassName();
/*  743 */     boolean externalizable = ti.isExternalizable();
/*      */     
/*      */
/*      */
/*  747 */     Object[] params = { className, null };
/*  748 */     Object object = createObjectInstance(params);
/*      */     
/*      */
/*  751 */     className = (String)params[0];
/*  752 */     PropertyProxy proxy = (PropertyProxy)params[1];
/*      */     
/*      */
/*  755 */     int objectId = rememberObject(object);
/*      */     
/*  757 */     if (externalizable)
/*      */     {
/*  759 */       readExternalizable(className, object); //<- call to readExternal
/*      */     }
/*      */     //...
/*      */   }

阅读上述文字后,大家应该对Adobe ColdFusion以及AMF有了基本的了解。

 

三、已有成果

Chris Gates(@Carnal0wnage)之前发表过一篇文章,详细介绍了如何渗透测试ColdFusion,是难得的一篇好文。

Wouter Coekaerts(@WouterCoekaerts)也在自己的博客中提到,反序列化不可信的AMF数据是非常危险的一种行为。

如果在Flexera/Secunia数据库中查找历史上已有的Adobe ColdFusion漏洞信息,你会发现这些漏洞大多数为XSS、XXE或者信息泄露漏洞。

最近的几个漏洞为:

通过RMI实现不可信数据的反序列化(CVE-2017-11283/4 by @nickstadb)
XXE(CVE-2017-11286 by Daniel Lawson of @depthsecurity)
XXE(CVE-2016-4264 by @dawid_golunski)

 

四、CVE-2017-3066

2017年,AgNO3 GmbH的Moritz Bechler以及我的小伙伴Markus Wulftange各自独立发现了Apache BlazeDS中的CVE-2017-3066漏洞。

这个漏洞的要点是Adobe Coldfusion中没有采用可信类的白名单机制。因此如果某个类位于Adobe ColdFusion的类路径(classpath)中,只要这些类符合Java Beans规范或者实现了java.io.Externalizable,那么就可以发送到服务器进行反序列化。Moritz和Markus两个人都发现,实现了java.io.Externalizable接口的JRE类(sun.rmi.server.UnicastRef2以及sun.rmi.server.UnicastRef)会在AMF3反序列化过程中触发一个TCP出站连接。当成功连接到攻击者的服务器后,程序会使用Java的原生反序列化方法(ObjectInputStream.readObject())来反序列化服务器的响应数据。这两个人都找到了一个非常好的“桥梁”,可以将AMF反序列化与Java的原生反序列化过程结合起来,这样许多公开的利用代码就可以用在这种场景中。大家可以访问Markus的博客了解关于该漏洞的详细信息。Apache通过flex.messaging.validators.ClassDeserializationValidator类引入了一种验证机制,其中包含一个默认的白名单,但也可以使用配置文件来进行配置。详细信息可以查阅Apache BlazeDS的发行说明

 

五、CVE-2017-3066的其他利用思路

本文开头提到过,我的小伙伴Thomas向我请求帮助,希望能够在没有出站连接的条件下同样能够利用这个漏洞。

先前我已经快速阅读过 Moritz Bechler发表的研究论文(Java Unmarshaller Security),论文中他分析了几种“Unmarshaller”,其中就包括BlazeDS。Moritz Bechler所提供的漏洞利用载荷不适用我们这种场景,因为classpath中缺少相关的库。

因此我还是决定按照自己常用的方法来挖掘。面对Java时,我首先会想到我最喜欢的“逆向工程工具”:Eclipse。Eclipse配上强大的反编译插件JD-Eclipse就足以应付动态以及静态分析场景。之前我也是一名开发者,习惯于使用IDE,这样开发起来能够更加方便,使非常低效且容易出错的反编译工作能够顺利推进。我新建了一个Java工程,将Adobe Coldfusion 12的所有jar文件以外部库方式添加到工程中。

首先我想到的是寻找对Java的ObjectInputStream.readObject方法的进一步调用情况。使用Eclipse可以轻松完成这个任务,只需要打开ObjectInputStream类,右键点击readObject()方法,然后点击“Open Call Hierarchy”即可。感谢JD-Eclipse以及反编译器的强大功能,Eclipse可以根据收集到的类信息,在没有源代码的情况下重新构造整个函数调用图。调用图最开始看起来规模非常庞大,但只要具备一定经验,你很快就能发现整张图中哪些节点比较有趣。经过几个小时的分析后,我找到了两个比较有希望的调用图。

基于SETTER方法的利用技术

第一个切入点源自于org.jgroups.blocks.ReplicatedTree类的setState(byte[] new_state)方法。

阅读这个方法的实现代码,我们可以想象第605行会出现什么状况。

/*      */   public void setState(byte[] new_state)
/*      */   {
/*  597 */     Node new_root = null;
/*      */     
/*      */
/*  600 */     if (new_state == null) {
/*  601 */       if (log.isInfoEnabled()) log.info("new cache is null");
/*  602 */       return;
/*      */     }
/*      */     try {
/*  605 */       Object obj = Util.objectFromByteBuffer(new_state);
/*  606 */       new_root = (Node)((Node)obj).clone();
/*  607 */       root = new_root;
/*  608 */       notifyAllNodesCreated(root);
/*      */     }
/*      */     catch (Throwable ex) {
/*  611 */       if (log.isErrorEnabled()) { log.error("could not set cache: " + ex);
/*      */       }
/*      */     }
/*      */   }

快速查看函数调用图后,我们确认调用链的最后一个节点是调用ObjectInputStream.readObject()

这里只需要注意一件事情:传递给setState()byte[]参数在0x0偏移处有一个额外的字节0x2,我们可以在org.jgroups.util.Util类的364行代码中看到这个信息。

/*      */   public static Object objectFromByteBuffer(byte[] buffer, int offset, int length) throws Exception
/*      */   {
/*  358 */     if (buffer == null) return null;
/*  359 */     if (JGROUPS_COMPAT)
/*  360 */       return oldObjectFromByteBuffer(buffer, offset, length);
/*  361 */     Object retval = null;
/*  362 */     InputStream in = null;
/*  363 */     ByteArrayInputStream in_stream = new ByteArrayInputStream(buffer, offset, length);
/*  364 */     byte b = (byte)in_stream.read();
/*      */     try {
/*      */       int len;
/*  367 */       switch (b) {
/*      */       case 0:
/*  369 */         return null;
/*      */       case 1:
/*  371 */         in = new DataInputStream(in_stream);
/*  372 */         retval = readGenericStreamable((DataInputStream)in);
/*  373 */         break;
/*      */       case 2:
/*  375 */         in = new ObjectInputStream(in_stream);
/*  376 */         retval = ((ObjectInputStream)in).readObject();
/*      */       //...
/*      */       }
/*      */      }
/*      */ }

漏洞利用情况如下图所示:

这个漏洞利用方法针对的是Adobe ColdFusion 12,并且只有启用JGroups时才能利用成功。

基于Externalizable的利用技术

第二个切入点源自于org.apache.axis2.util.MetaDataEntry类的readExternal方法。

在代码中的297行,程序会调用SafeObjectInputStream.install(inObject)方法。

/*     */   public static SafeObjectInputStream install(ObjectInput in)
/*     */   {
/*  62 */     if ((in instanceof SafeObjectInputStream)) {
/*  63 */       return (SafeObjectInputStream)in;
/*     */     }
/*  65 */     return new SafeObjectInputStream(in) ;
/*     */   }

在这个函数中,我们的AMF3Input实例属于org.apache.axis2.context.externalize.SafeObjectInputStream类的一个实例。

/*     */   private Object readObjectOverride()
/*     */     throws IOException, ClassNotFoundException
/*     */   {
/* 318 */     boolean isActive = in.readBoolean();
/* 319 */     if (!isActive) {
/* 320 */       if (isDebug) {
/* 321 */         log.debug("Read object=null");
/*     */       }
/* 323 */       return null;
/*     */     }
/* 325 */     Object obj = null;
/* 326 */     boolean isObjectForm = in.readBoolean();
/* 327 */     if (isObjectForm)
/*     */     {
/* 329 */       if (isDebug) {
/* 330 */         log.debug(" reading using object form");
/*     */       }
/* 332 */       obj = in.readObject();
/*     */     } else {
/* 334 */       if (isDebug) {
/* 335 */         log.debug(" reading using byte form");
/*     */       }
/*     */       
/* 338 */       ByteArrayInputStream bais = getByteStream(in);
/*     */       
/*     */
/* 341 */       ObjectInputStream tempOIS = createObjectInputStream(bais);
/* 342 */       obj = tempOIS.readObject();
/* 343 */       tempOIS.close();
/* 344 */       bais.close();
/*     */     }
/*     */   //...
/*     */   }

上述代码的341行会创建org.apache.axis2.context.externalize.ObjectInputStreamWithCL类的一个新的实例,这个类扩展了(extend)标准的java.io.ObjectInputStream类。在第342行,我们最终实现了对readObject()方法的调用。

漏洞利用情况如下图所示:

这种漏洞利用方法适用于Adobe ColdFusion 11以及12。

COLDFUSIONPWN工具

为了让我们的工作更加轻松,我开发了一款简单的工具:ColdFusionPwn。这是一款命令行工具,我们可以通过该工具生成序列化后的AMF消息。该工具可以与Chris Frohoff的ysoserial配合使用生成gadget。

 

六、总结

毋庸置疑,反序列化不可信的输入数据并不是一件好事。从攻击者的角度来看,利用反序列化漏洞是一项富有挑战性的任务,因为他们需要找到“正确”的对象(即gadget),才能触发漏洞、构造利用路径,然而这也是非常有趣的一个探索历程。

顺便提一句:如果你想深入了解服务端的Java利用技术,理解Java中的各种反序列化漏洞,正确开展静态以及动态分析,那么你应该会对我们即将推出的“Java高级利用技术”课程感兴趣。

(完)