Filter/Servlet型內存馬的掃描抓捕與查殺
0x01 背景
在內存馬橫行的當下,藍隊or應急的師傅如何能快速判斷哪些Filter/Servlet是內存馬,分析內存馬的行為功能是什么?最終又如何不重啟的將其清除?紅隊師傅又如何抓鋪其他師傅的內存馬為自己用,亦或是把師傅的內存馬踢掉?
在當下攻防對抗中,一直缺少著針對內存馬掃描,捕捉與查殺的輔助腳本。下面就以Tomcat 8.5.47為例子,分享下編寫方法,其他中間件萬變不離其宗。
考慮到Agent技術針對紅隊來說比較重,我們這次使用jsp技術來解決以上問題。
0x02 掃描Filter和Servlet
要想掃描web應用內存中的Filter和Servlet,我們必須知道它們存儲的位置。通過查看代碼,我們知道StandardContext對象中維護的是一個
和Filter相關的是filterDefs和filterMaps兩個屬性。這兩個屬性分別維護著全局Filter的定義,以及Filter的映射關系。

和Servlet相關的是children和servletMappings兩個屬性。這兩個屬性分別維護這全家Servlet的定義,以及Servlet的映射關系。


其他request對象中就存儲這StandardContext對象。
request.getSession().getServletContext() {ApplicationContextFacade} -> context {ApplicationContext} -> context {StandardContext} * filterDefs * filterMaps * children * servletMappings
所以我們只需要通過反射遍歷request,最終就可以拿到Filter和Servlet的如下信息。
Filter/Servlet名匹配路徑Class名ClassLoaderClass文件存儲路徑。內存中Class字節碼(方便反編譯審計其是否存在惡意代碼)該Class是否有對應的磁盤文件(判斷內存馬的重要指標)
具體反射遍歷代碼放文末github,這里值得一提是拿到Class名通過如下方法就能拿到其被加載到內存中的字節碼內容。
byte[] classBytes = Repository.lookupClass(Class.forName("me.gv7.Memshell")).getBytes();
0x03 注銷Filter內存馬
通過分析調試Tomcat源碼,我們知道Tomcat注銷filter其實就是將該Filter從全局filterDefs和filterMaps中清除掉。具體的操作分別如下removeFilterDef和removeFilterMap兩個方法中。
//org.apache.catalina.core.StandardContext#removeFilterDefpublic void removeFilterDef(FilterDef filterDef) { synchronized(this.filterDefs) { this.filterDefs.remove(filterDef.getFilterName()); } this.fireContainerEvent("removeFilterDef", filterDef);}
//org.apache.catalina.core.StandardContext#removeFilterMappublic void removeFilterMap(FilterMap filterMap) { this.filterMaps.remove(filterMap); this.fireContainerEvent("removeFilterMap", filterMap);}
我們只需要反射調用它們即可注銷Filter。
public synchronized void deleteFilter(HttpServletRequest request,String filterName) throws Exception{ Object standardContext = getStandardContext(request);
// org.apache.catalina.core.StandardContext#removeFilterDef HashMap filterConfig = getFilterConfig(request); Object appFilterConfig = filterConfig.get(filterName); Field _filterDef = appFilterConfig.getClass().getDeclaredField("filterDef"); _filterDef.setAccessible(true); Object filterDef = _filterDef.get(appFilterConfig); Method removeFilterDef = standardContext.getClass().getDeclaredMethod("removeFilterDef", new Class[]{org.apache.tomcat.util.descriptor.web.FilterDef.class}); removeFilterDef.setAccessible(true); removeFilterDef.invoke(standardContext,filterDef);
// org.apache.catalina.core.StandardContext#removeFilterMap Object[] filterMaps = getFilterMaps(request); for(Object filterMap:filterMaps){ Field _filterName = filterMap.getClass().getDeclaredField("filterName"); _filterName.setAccessible(true); String filterName0 = (String)_filterName.get(filterMap); if(filterName0.equals(filterName)){ Method removeFilterMap = standardContext.getClass().getDeclaredMethod("removeFilterMap", new Class[]{org.apache.catalina.deploy.FilterMap.class}); removeFilterDef.setAccessible(true); removeFilterMap.invoke(standardContext,filterMap); } }}
0x04 注銷Servlet內存馬
注銷Servlet的原理也是類似,將該Servlet從全局servletMappings和children中清除掉即可。在Tomcat源碼中對應的是removeServletMapping和removeChild方法。
//org.apache.catalina.core.StandardContext#removeServletMappingpublic void removeServletMapping(String pattern) { String name = null; synchronized(this.servletMappingsLock) { name = (String)this.servletMappings.remove(pattern); }
Wrapper wrapper = (Wrapper)this.findChild(name); if (wrapper != null) { wrapper.removeMapping(pattern); }
this.fireContainerEvent("removeServletMapping", pattern);}
//org.apache.catalina.core.StandardContext#removeChildpublic void removeChild(Container child) { if (!(child instanceof Wrapper)) { throw new IllegalArgumentException(sm.getString("standardContext.notWrapper")); } else { super.removeChild(child); }}
我們只需要反射調用它們即可注銷Servlet。
public synchronized void deleteServlet(HttpServletRequest request,String servletName) throws Exception{ HashMap childs = getChildren(request); Object objChild = childs.get(servletName); String urlPattern = null; HashMap servletMaps = getServletMaps(request); for(Map.Entry servletMap:servletMaps.entrySet()){ if(servletMap.getValue().equals(servletName)){ urlPattern = servletMap.getKey(); break; } }
if(urlPattern != null) { // 反射調用 org.apache.catalina.core.StandardContext#removeServletMapping Object standardContext = getStandardContext(request); Method removeServletMapping = standardContext.getClass().getDeclaredMethod("removeServletMapping", new Class[]{String.class}); removeServletMapping.setAccessible(true); removeServletMapping.invoke(standardContext, urlPattern); // Tomcat 6必須removeChild 789可以不用 // 反射調用 org.apache.catalina.core.StandardContext#removeChild Method removeChild = standardContext.getClass().getDeclaredMethod("removeChild", new Class[]{org.apache.catalina.Container.class}); removeChild.setAccessible(true); removeChild.invoke(standardContext, objChild); }}
0x05 演示
我們只需要把編寫好的tomcat-memshell-scanner.jsp放到可能被注入內存的web項目中,然后通過瀏覽器訪問即可。假設掃描結果如下:

通過分析掃描出的信息,可知filter-b2b1cad2-44be-4f43-8db0-bd43da5ad368是Filter型內存馬,原因如下:
1. classLoader是可疑的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader,這是反序列化漏洞執行代碼用的classLoader。
2. class在磁盤中沒有對應的class文件,只駐留在內存。
/favicon.ico是Servlet型內存馬,判斷原因如下。
1. classLoader是自定義classLoader,當下比較流行的java webshell基本都是自定義了class loader來實現任意代碼執行。
2. class在磁盤中沒有對應的class文件,只駐留在內存。
最后我們可以dump出那么對應的class,反編譯看代碼分析filter-b2b1cad2-44be-4f43-8db0-bd43da5ad368是Filter型cmd內存馬,/favicon.ico是Servlet型哥斯拉內存馬。
原文鏈接:https://gv7.me/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/