漏洞描述:
tomcat是Apache組織開發的中小型的JavaEE服務器,它實作了servlet,JSP等javaEE規范,可以提供web資源訪問服務,tomcat主要提供了兩種通信方式訪問web資源:http協議和AJP協議,
tomcat服務器默認會在8009埠開放了一個AJP服務,客戶端(瀏覽器)通過與tomcat服務器的8009埠建立AJP通信,可以訪問服務器的web資源,但是tomcat的AJP協議在設計上存在一些缺陷,攻擊者通過這點可以構造惡意的請求進行檔案包含操作,從而讀取tomcat服務器webapp目錄下的任意檔案,
漏洞環境:
apache-tomcat-8.0.50
apache-tomcat-8.0.50-src
漏洞復現:
tomcat的conf目錄下有一個server.xml組態檔,該檔案中有兩個onnector,一個是用于處理http協議,另一個是處理AJP協議,tomcat服務器會根據客戶端的請求使用對應的Connector來處理,

根據該漏洞的特點,可以通過網路工具掃描目標服務器8009埠來判斷是否存在漏洞,我用nmap工具試了一下,可以掃出目標服務器的8009埠對應的ajp服務,nmap還是挺強大的,

啟動tomcat服務器,然后執行poc讀取tomcat服務器的目錄/WEB-INF/web.xml檔案,可以看到成功讀取到web.xml檔案,說明漏洞復現成功,

由于AJP協議是基于tcp協議無法使用burpsuite工具抓取其資料包,只能通過wireshark工具抓包,這里我們只需關注AJP協議資料包即可,通過wireshark的過濾規則把所有AJP資料包都過濾出來,可以看到AJP協議的資料包格式還是和HTTP協議有些類似的

整個通信程序可以分為三部分:建立tcp連接階段,AJP通信階段,釋放tcp連接階段,這里我們重點關注AJP通信程序,
Frame 61是客戶端(瀏覽器)發送的第一個AJP資料包,選中Frame 61資料包查看其AJP資料包的詳細資訊,分析AJP資料包發送了哪些內容:

可以看到AJP請求的head部分中Method欄位中指定了AJP協議的請求方式是GET,URI表示AJP請求的web資源,Version表示AJP請求的版本資訊,最后是AJP請求的資料部分,
在整個AJP請求資料包中,這一部分是poc程式漏洞利用代碼構造的惡意資料

漏洞分析:
當tomcat服務器收到AJP請求后會交由AjpProcessor類來處理,也就是說,AjpProcessor類是用于處理AJP協議的,但真正處理AJP請求資料的是AjpProcessor的父類org.apache.coyote.ajp.AbstractAjpProcessor,
AbstractAjpProcessor類中有一個process方法用于處理AJP請求,但是process方法內部邏輯過于復雜,無需深入分析,這里我們記住一點:該方法內部會回圈呼叫一個prepareRequest方法處理AJP請求資料,如下所示:

在prepareRequest方法內部中,呼叫了setAttribute方法將AJP請求中的資料部分封裝到request請求中的attributes屬性中,然后tomcat會將AJP請求分發給Servlet處理,而tomcat服務器的web.xml默認有兩個Servlet,分別為處理JSP請求的JspServlet和處理所有請求的DefaultServlet,
由于POC發送的AJP請求的RUI欄位的資源是jsp

根據tomcat服務器的web.xml檔案中JSP請求的映射規則,這個請求會被JspServlet匹配到,那么這個AJP請求會分發給JspServlet處理,

然后JspServlet會呼叫service方法處理AJP請求:
@SuppressWarnings("deprecation") // Use of JSP_FILE to be removed in 8.5.x
@Override
public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//判斷jspFile是否為null
String jspUri = jspFile;
if (jspUri == null) {
// JSP specified via <jsp-file> in <servlet> declaration and
// supplied through custom servlet container code
String jspFile = (String) request.getAttribute(Constants.JSP_FILE);
if (jspFile != null) {
jspUri = jspFile;
request.removeAttribute(Constants.JSP_FILE);
}
}
if (jspUri == null) {
//從request域中獲取servlet_path
jspUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
if (jspUri != null) {
//從request域中獲取path_info
String pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
//將servlet_path和path_info拼接成uri路徑
if (pathInfo != null) {
jspUri += pathInfo;
}
} else {
jspUri = request.getServletPath();
String pathInfo = request.getPathInfo();
if (pathInfo != null) {
jspUri += pathInfo;
}
}
}
if (log.isDebugEnabled()) {
log.debug("JspEngine --> " + jspUri);
log.debug("\t ServletPath: " + request.getServletPath());
log.debug("\t PathInfo: " + request.getPathInfo());
log.debug("\t RealPath: " + context.getRealPath(jspUri));
log.debug("\t RequestURI: " + request.getRequestURI());
log.debug("\t QueryString: " + request.getQueryString());
}
try {
boolean precompile = preCompile(request);
//然后呼叫serviceJspFile方法
serviceJspFile(request, response, jspUri, precompile);
} catch (RuntimeException e) {
throw e;
} catch (ServletException e) {
throw e;
} catch (IOException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(e);
}
}
service方法會判斷jspFile是否為null,如果jspFile為null的話,就會呼叫getAttribute方法從request域的attribute屬性中獲取JSP資源路徑(INCLUDE_SERVLET_PATH和INCLUDE_PATH_INFO這兩個常量的值是可控的),最后得到一個這樣的uri路徑:/WEB-INF/web.xml ,
這兩個常量的定義如下:
static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
接著呼叫了一個preCompile方法,該方法會獲取request請求中的引數部分,大多數情況下都會回傳false
boolean preCompile(HttpServletRequest request) throws ServletException {
//獲取request請求中的引數部分
String queryString = request.getQueryString();
//引數為空則直接回傳false
if (queryString == null) {
return (false);
}
//......
}
將得到的uri路徑傳給了serviceJspFile方法引數jspUri
private void serviceJspFile(HttpServletRequest request , HttpServletResponse response , String jspUri , boolean precompile)throws ServletException, IOException {
//根據uri路徑獲取JSP
JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
if (wrapper == null) {
synchronized(this) {
wrapper = rctxt.getWrapper(jspUri);
if (wrapper == null) {
//檢查uri指定的jsp資源是否存在
if (null == context.getResource(jspUri)) {
handleMissingResource(request, response, jspUri);
return;
}
//存在就決議jsp資源
wrapper = new JspServletWrapper(config, options, jspUri,rctxt);
rctxt.addWrapper(jspUri,wrapper);
}
}
}
try {
wrapper.service(request, response, precompile);
} catch (FileNotFoundException fnfe) {
handleMissingResource(request, response, jspUri);
}
}
serviceJspFile方法主要是根據引數jspUri指定的jsp資源的路徑決議JSP并進行展示,getResource方法會根據jspUri中的uri路徑判斷指定的檔案是否存在,如果存在就會決議該路徑指定的資源,
如何通過檔案包含漏洞來達到RCE執行目的?
前提是必須上傳一個嵌入RCE代碼的JSP檔案到目標tomcat服務器上,當我們訪問這個JSP檔案時,JspServlet會將指定的JSP檔案轉換成java代碼的.class檔案,然后執行該位元組碼檔案時就會執行RCE代碼彈出計算機(具體原理可參考JspServlet原始碼執行原理),

新建一個123.jsp檔案插入java代碼上傳到目標tomcat服務器的WEB-INF目錄下
<%--
Created by IntelliJ IDEA.
User: yl
Date: 2021/8/8
Time: 9:51
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%-- 嵌入RCE代碼 --%>
<% Runtime.getRuntime().exec("calc.exe"); %>
成功彈出計算機

除了JspServlet之外,DefaultServlet也可以產生任意檔案包含漏洞,這兩種方式的利用思路大致相同,這里就不再繼續分析了,感興趣的同學可以自行分析,
漏洞修復:
1. 在server.xml中注釋掉8009埠的Connector,禁用AJP協議
2. 升級最新版本
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293515.html
標籤:其他
上一篇:攻防世界 WEB upload1
