译文声明

本文是翻译文章,文章原作者Matthias Kaiser ,文章来源:https://codewhitesec.blogspot.hk/
原文地址:https://codewhitesec.blogspot.hk/2018/03/exploiting-adobe-coldfusion.html

 

一、前言

在最近一次渗透测试任务中,我的小伙伴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高级利用技术”课程感兴趣。