Adobe ColdFusion漏洞复现
2024-03-28 19:47:02

环境搭建

由于官方的镜像是新版了,这里找到个旧的镜像:

1
docker run -p 15005:5005 -p 18080:8080 -dt -e acceptEULA=YES -e password=admin vulfocus/vcpe-1.0-a-adobe-coldfusion:2023.0.0.330468-openjdk-release

上面这个旧版的就打包:/opt/apache-tomcat-9.0.78/webapps 这里就行

新版的直接由官方:

https://hub.docker.com/r/adobecoldfusion/coldfusion

可修改配置文件开启 debug:

1
/opt/coldfusion/cfusion/bin/jvm.config

然后把整个 /opt/coldfusion 都打包下来就好了。

调用过程分析

*** 这里建议先把所有 jar 加到同一个文件夹先,比较好引入。***

首先是 web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<servlet-mapping id="coldfusion_mapping_4">
<servlet-name>CFCServlet</servlet-name>
<url-pattern>*.cfc</url-pattern>
</servlet-mapping>
<servlet id="coldfusion_servlet_5">
<servlet-name>CFCServlet</servlet-name>
<display-name>CFC Processor</display-name>
<description>Compiles and executes CF web components</description>
<servlet-class>coldfusion.bootstrap.BootstrapServlet</servlet-class>
<init-param id="InitParam_1034013110657ax">
<param-name>servlet.class</param-name>
<param-value>coldfusion.xml.rpc.CFCServlet</param-value>
</init-param>
<load-on-startup>10</load-on-startup>
</servlet>

BootstrapServlet 类会转到 servlet.class 指向的类里,所以直接看 coldfusion.xml.rpc.CFCServlet 就好。

这个类的话看了一下,主要是 filter 的组装,然后就会进入到 filter.invoke了。

filter 的话,比较关键的点有几个

首先是:

1
coldfusion.filter.GlobalsFilter

这个 Filter 会做一些全局属性的组装,就是把GET POST 里的参数都放在一个 scope 里,方便后面框架的取用

image-20240330034336523

image-20240330034418244

了解一下就好了,然后就是这个:

1
coldfusion.filter.PathFilter#invoke

image-20240330033739505

在这里会根据 URL 的路由拼接上路径去找到对应的文件然后载入,具体载入过程就不详细分析了。

然后就是最后的一个 Filter

1
coldfusion.filter.ComponentFilter

这个 filter 中实现了方法的调用:

image-20240330034537028

*** cfccfm 就是 class 文件,但是他是很多个文件 class 二进制文件拼接起来的。***

用 GPT 写了个脚本进行分割:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def split_file_by_marker(file_path, output_folder, marker=b'\xCA\xFE\xBA\xBE'):
try:
with open(file_path, 'rb') as f:
file_content = f.read()

parts = file_content.split(marker)

# 第一个分割可能是文件开始前的数据,如果它是空的就忽略它
if len(parts[0]) == 0:
parts.pop(0)

for i, part in enumerate(parts, start=1):
output_path = f"{output_folder}/class{i}.class"
with open(output_path, 'wb') as output_file:
# 对于除第一个文件外,其他文件需要把marker加回去
if i > 1:
output_file.write(marker)
output_file.write(part)
print(f"文件已保存:{output_path}")

except FileNotFoundError:
print(f"文件未找到:{file_path}")
except Exception as e:
print(f"处理文件时发生错误:{e}")

# 示例使用
file_path = 'servermanager.cfc'
output_folder = './out' # 替换为你想要保存分割后的文件的目录
split_file_by_marker(file_path, output_folder)

几个漏洞复现

CVE-2024-20767

这个是个任意文件读的,首先是找到这个类:

image-20240330021029244

1
coldfusion.monitor.PMSGenericServlet

image-20240330021132460

看到两处,第一处是验证,第二处是 logging

image-20240330021200679

这个函数可以读取文件,没有过滤的,然后看看 validateSecret 函数:

1
2
3
4
private boolean validateSecret(HttpServletRequest request) {
String uuid = request.getHeader("uuid");
return uuid != null && uuid.equalsIgnoreCase(Configuration.INSTANCE.getUuid());
}

这个验证请求头的 uuidheader 里是否一致。

那么这个怎么来呢

文件位置:CFIDE/adminapi/_servermanager/servermanager.cfc

分割以后能找到其中有个类叫做:cfservermanager2ecfc1905146815$funcGETHEARTBEAT

image-20240330023630337

可以看到这里调用了 MONITORINGSERVICE#getHeartBeat

这里建议把所有的 jar 都拖到一个 libs 里,然后就能找到这里地方:

1
coldfusion.monitor.module.MonitoringServiceImpl#getHeartBeat

image-20240330023746416

可以看到这里就是输出了 uuid

(当然,以上过程都是看答案找过程罢了,真正要理解还是要自己想想如何找的)

然后就是找到上面的 PMS:

1
2
3
4
<servlet-mapping id="coldfusion_mapping_pms">
<servlet-name>PMSGenericServlet</servlet-name>
<url-pattern>/pms</url-pattern>
</servlet-mapping>

这里是可以 Debug 的,回到这个函数:

1
coldfusion.monitor.PMSGenericServlet#doGet

image-20240330024220704

然后就是数据包:

1
2
3
4
GET /pms?module=logging&file_name=../../../../../../../../../../../../../etc/passwd&number_of_lines=999 HTTP/1.1
Host: 127.0.0.1:18080
uuid: f171e4a1-a37a-4c04-87df-bca72f6fe87a

修复方法

这个修复比较逆天,在新版中已经没有 CFIDE/adminapi/_servermanager 这个文件夹了,好像把 _servermanager 都删了。

一些额外的分析

其实这里还是有一些限制的,会发现不是所有的 servermanager.cfc 下面的方法都能调用。

在根据方法名获取到方法以后,会进入到这里:

1
coldfusion.runtime.TemplateProxy#invoke(coldfusion.runtime.UDFMethod, String, Object[], Map, coldfusion.runtime.CfJspPage, coldfusion.filter.FusionContext)

image-20240330030721077

第二个箭头是调用的地方,上面有个 checkAccess函数:

image-20240330030822829

这里可以看到不是 3 就会做一些判断,具体做了什么判断就先不分析了,也就是说我们需要找到 getAccess 返回 3 的 方法,比如这个:

image-20240330030922401

但是这个也不行,直接执行方法:

1
/CFIDE/adminapi/_servermanager/servermanager.cfc?method=getVersionString

会返回说权限不够:

image-20240330031004676

这是因为在 runFunction 实际执行中还是做了一个权限验证:

image-20240330031034649

因此导致不行执行,刚好的是上面提到的 getHeartBeat 没有校验,所以才能获得 UUID

CVE-2023-26360

参考:https://xz.aliyun.com/t/13392?time__1311=mqmxnDBG0QqWwx05DIYYK0%3D9CuhleF4D&alichlgref=https%3A%2F%2Fwww.google.com%2F#toc-0

直接搭建一个官方的:

1
docker run -p 25005:5005 -p 28500:8500 -dt -e acceptEULA=YES -e password=admin adobecoldfusion/coldfusion2018:2018.0.15

这里虽然说是版本 < 2021.0.7 都行,但是我搭建了 2021.0.5 的打 payload 好像失败了,我就直接搭了个 2018 的了。

这个漏洞点在上面提到过的 ComponentFilter

1
coldfusion.filter.ComponentFilter#invoke

其中有一处:

image-20240330035024094

首先是 URL 中传递一个 _cfclient,把它设成 true 之后进入 deserializeJSON,解析 JSON 这一块会有很多逻辑,可以直接定位到关键点:

1
coldfusion.runtime.JSONUtils#convertToTemplateProxy

image-20240330044047215

这里会根据传入的 classname 去解析得到文件,然后会进行类似模板编译的操作。

这个漏洞有个 RCE 的方法就是写入到 log 文件,然后把 log 编译出来就好了,可以看看上面提到的参考文章中有。

CVE-2023-29300

参考:

https://xz.aliyun.com/t/13413?time__1311=mqmxnDBQqDq7q405d4%2BxCTPqIODfxx2OuiD&alichlgref=https%3A%2F%2Fwww.google.com%2F#toc-0

这个漏洞环境用上面一样的也行。

漏洞点还是在 ComponentFilter ,只不过这次在上面两行:

image-20240330044900099

1
coldfusion.filter.FilterUtils#GetArgumentCollection

image-20240330044942472

传入的 argumentCollection 会做 json 解析或者进行 WDDXDeserialize,这看起来是一个 xml 一样的解析:

image-20240330045103445

这里就不装模作样的分析了,直接找到最重要的标签:

1
coldfusion.wddx.StructHandler

image-20240330045600523

首先看 getClassBySignature 函数,这个函数获取 Class 对象 ,然后返回回去进行实例化:

image-20240330045501241

然后再看上面有调用 setBeanProperties 函数:

image-20240330050121134

这里就能获取 WriteMethod 然后调用 setter ,比较经典的就是利用 JdbcRowSetImpl 进行 jndi 了。

修复方法

有很多同名类啊,这里找到正确的 jar 文件:chf20230007.jar

在做 wddx 解码的时候会先到这里:

1
coldfusion.wddx.DeserializerWorker#startElement

image-20240330164809413

如果是 struct 标签的话就进入验证,调用栈如下:

1
2
3
4
coldfusion.wddx.DeserializerWorker#validateWddxFilter
-> coldfusion.wddx.WddxFilter#validateAllowedClass
-> coldfusion.wddx.WddxFilter#invoke
-> coldfusion.wddx.WddxFilter#checkInput

image-20240330170515420

这里进行了白名单验证,不在白名单返回 UNDECIDED,然后返回回来以后报错了:

image-20240330170558168

这里看着好像就过滤了 struct 标签,不知道其他标签是否有过滤,有机会可以看看

Prev
2024-03-28 19:47:02