漏洞原理:
Tomcat 配置了兩個 Connecto,它們分別是 HTTP 和 AJP :HTTP 默認端口爲 8080,處理 http 請求,而 AJP 默認端口 8009,用於處理 AJP 協議的請求,而 AJP 比 http 更加優化,多用於反向、集羣等,漏洞由於 Tomcat AJP 協議存在缺陷而導致,攻擊者利用該漏洞可通過構造特定參數,讀取服務器 webapp 下的任意文件以及可以包含任意文件,如果有某上傳點,上傳圖片馬等等,即可以獲取 shell。
漏洞版本:
Apache Tomcat 6
Apache Tomcat 7 < 7.0.100
Apache Tomcat 8 < 8.5.51
Apache Tomcat 9 < 9.0.31
漏洞利用:
https://github.com/0nise/CVE-2020-1938
https://github.com/nibiwodong/CNVD-2020-10487-Tomcat-ajp-POC
https://github.com/Kit4y/CNVD-2020-10487-Tomcat-Ajp-lfi-Scanner
https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi/
漏洞修復(官方方案):
1. 如未使用 Tomcat AJP 協議:
如未使用 Tomcat AJP 協議,可以直接將 Tomcat 升級到 9.0.31、8.5.51 或 7.0.100 版本進行漏洞修復。
如無法立即進行版本更新、或者是更老版本的用戶,建議直接關閉 AJPConnector,或將其監聽地址改爲僅監聽本機 localhost。
具體操作:
(1)編輯 <CATALINA_BASE>/conf/server.xml,找到如下行(<CATALINA_BASE> 爲 Tomcat 的工作目錄):
<Connector port="8009"protocol="AJP/1.3" redirectPort="8443" />
(2)將此行註釋掉(也可刪掉該行)
<!--<Connectorport="8009" protocol="AJP/1.3"redirectPort="8443" />-->
(3)保存後需重新啓動,規則方可生效。
1. 如果使用了 Tomcat AJP 協議:
建議將 Tomcat 立即升級到 9.0.31、8.5.51 或 7.0.100 版本進行修復,同時爲 AJP Connector 配置 secret 來設置 AJP 協議的認證憑證。例如(注意必須將 YOUR_TOMCAT_AJP_SECRET 更改爲一個安全性高、無法被輕易猜解的值):
<Connector port="8009"protocol="AJP/1.3" redirectPort="8443"address="YOUR_TOMCAT_IP_ADDRESS" secret="YOUR_TOMCAT_AJP_SECRET"/>
如無法立即進行版本更新、或者是更老版本的用戶,建議爲 AJPConnector 配置 requiredSecret 來設置 AJP 協議認證憑證。例如(注意必須將 YOUR_TOMCAT_AJP_SECRET 更改爲一個安全性高、無法被輕易猜解的值):
<Connector port="8009"protocol="AJP/1.3" redirectPort="8443"address="YOUR_TOMCAT_IP_ADDRESS"requiredSecret="YOUR_TOMCAT_AJP_SECRET" />
漏洞修復(Apache ajp 版本):
根據一位小夥伴(文章評論的第一位小夥伴)的反饋,又重新查了下Apache的反向代理方式,發現主要有以下三種:
1、jk方式集成 //此方式支持AJP進行參數配置(小夥伴提供)
2、ajp_proxy //此方式目前沒有找到相關配置方案
3、http_proxy //此方式無需ajp,可以直接將ajp關閉
jk方式配置依照配置文件應該是(待驗證,配置方法可以參考這邊文章->傳送門):
在worker.properties 文件中添加worker.secrect=YOUR_TOMCAT_AJP_SECRET
Apache2 使用 Http 實現類似 ip_hash 機制 session 軟負載:
1.啓用模塊
打開 httpd.conf 啓動如下模塊:
LoadModule headers_module modules/mod_headers.so
2.在 conf 目錄下新增配置文件 balance.conf,內容如下:
#提供基礎的代理功能
LoadModule proxy_module modules/mod_proxy.so
#提供負載均衡的功能
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
#代理http協議
LoadModule proxy_http_module modules/mod_proxy_http.so
#負載均衡的算法模塊
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
#兼容低版本訪問
LoadModule access_compat_module modules/mod_access_compat.so
ProxyRequests Off
#啓用類似ip_hash機制配置
ProxyPreserveHost on
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
#代理關聯配置loadfactor可以分發請求權重,loadfactor越大,權重越大
<Proxy balancer://mycluster>
BalancerMember http://localhost:8001 loadfactor=1 route=tomcat7_1
BalancerMember http://localhost:8002 loadfactor=1 route=tomcat7_2
#熱部署,當着備份服務,當tomcat7_1和tomcat7_2死掉的時候,就自動訪問tomcat7_3
#BalancerMember http://localhost:9080 loadfactor=1 route=tomcat7_3 status=+H
</Proxy>
#啓用類似ip_hash機制配置
ProxyPass / balancer://mycluster/ stickysession=ROUTEID nofailover=On
#負載均衡控制檯,通過http://localhost/balancer-manager 訪問
<Location /balancer-manager>
SetHandler balancer-manager
Order Deny,Allow
Allow from all
#Allow from localhost
</Location>
3.httpd.conf 引入步驟 2 中的配置文件
include conf/balance.conf
4.重啓 Apache
5.附贈測試 session 保持的 JSP 頁面一枚
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="java.util.*" %>
<html><head><title>Cluster Test8002</title></head>
<body>
<h3>this is tomcat3!!</h3>
<%
out.println("<p> SESSION ID : " + session.getId()+"</p>");
String name = request.getParameter("name");
if (name != null && name.length() > 0) {
String value = request.getParameter("value");
session.setAttribute(name, value);
}
out.print("<table border = '1'>");
out.print("<tr><th>session key </th><th>session value </th></tr>");
Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {
String key = names.nextElement();
String value = session.getAttribute(key).toString();
System.out.print(key + " --- " + value);
out.print("<tr><td>"+key+"</td><td>"+value+"</td></tr>");
}
out.print("</table>");
%>
<br />
<form action="testCluster.jsp" method="post">
session key :<input type=text name="name">
session value:<input type=text name="value">
<input type=submit value="添加">
</form>
</body>
</html>