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