Ysoserial Click1利用鏈分析

深信服千里目發表於2021-09-27

click1 gadget構造思路是基於Commons-Collections2的Sink點(com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl)和source點(java.util.PriorityQueue)。Commons-Collections2使用TransformingComparator方法作為PriorityQueue類中的comparator屬性值。再Click1 gadget中,作者使用org.apache.click.control.Column$ColumnComparator類作為替代。在Commons-Collections2分析中,可知java.util.PriorityQueue反序列化可以呼叫org.apache.click.control.Column$ColumnComparator#compare方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int compare(Object row1, Object row2) {
    this.ascendingSort = this.column.getTable().isSortedAscending() ? 1 : -1;
    Object value1 = this.column.getProperty(row1);
    Object value2 = this.column.getProperty(row2);
    if (value1 instanceof Comparable && value2 instanceof Comparable) {
        return !(value1 instanceof String) && !(value2 instanceof String) ? ((Comparable)value1).compareTo(value2) * this.ascendingSort : this.stringCompare(value1, value2) * this.ascendingSort;
    } else if (value1 != null && value2 != null) {
        return value1.toString().compareToIgnoreCase(value2.toString()) * this.ascendingSort;
    } else if (value1 != null && value2 == null) {
        return 1 * this.ascendingSort;
    } else {
        return value1 == null && value2 != null ? -1 * this.ascendingSort : 0;
    }
}

其中compare方法傳入的引數,即是構造好的惡意TemplatesImpl物件。

呼叫this.column.getProperty(row1)方法

1
2
3
public Object getProperty(Object row) {
    return this.getProperty(this.getName(), row);
}

呼叫this.getName()方法獲取Column#name屬性值,並呼叫this.getProperty(name , row)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public Object getProperty(String name, Object row) {
    if (row instanceof Map) {
        Map map = (Map)row;
        Object object = map.get(name);
        if (object != null) {
            return object;
        } else {
            String upperCaseName = name.toUpperCase();
            object = map.get(upperCaseName);
            if (object != null) {
                return object;
            } else {
                String lowerCaseName = name.toLowerCase();
                object = map.get(lowerCaseName);
                return object != null ? object : null;
            }
        }
    } else {
        if (this.methodCache == null) {
            this.methodCache = new HashMap();
        }
 
        return PropertyUtils.getValue(row, name, this.methodCache);
    }
}

由於傳入的TemplatesImpl物件不是Map的子類,直接跳過if判斷,在為methodCache屬性初始化HashMap型別物件後,呼叫PropertyUtils.getValue(row, name, this.methodCache)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static Object getValue(Object source, String name, Map cache) {
    String basePart = name;
    String remainingPart = null;
    if (source instanceof Map) {
        return ((Map)source).get(name);
    } else {
        int baseIndex = name.indexOf(".");
        if (baseIndex != -1) {
            basePart = name.substring(0, baseIndex);
            remainingPart = name.substring(baseIndex + 1);
        }
 
        Object value = getObjectPropertyValue(source, basePart, cache);
        return remainingPart != null && value != null ? getValue(value, remainingPart, cache) : value;
    }
}

首先將傳入的name引數值賦給basePart變數。並在呼叫getObjectPropertyValue方法時,作為引數傳入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private static Object getObjectPropertyValue(Object source, String name, Map cache) {
    PropertyUtils.CacheKey methodNameKey = new PropertyUtils.CacheKey(source, name);
    Method method = null;
 
    try {
        method = (Method)cache.get(methodNameKey);
        if (method == null) {
            method = source.getClass().getMethod(ClickUtils.toGetterName(name));
            cache.put(methodNameKey, method);
        }
 
        return method.invoke(source);
    } catch (NoSuchMethodException var13) {
        try {
            method = source.getClass().getMethod(ClickUtils.toIsGetterName(name));
            cache.put(methodNameKey, method);
            return method.invoke(source);
        } catch (NoSuchMethodException var11) {
            String msg;
            try {
                method = source.getClass().getMethod(name);
                cache.put(methodNameKey, method);
                return method.invoke(source);
            } catch (NoSuchMethodException var9) {
                msg = "No matching getter method found for property '" + name + "' on class " + source.getClass().getName();
                throw new RuntimeException(msg);
            } catch (Exception var10) {
                msg = "Error getting property '" + name + "' from " + source.getClass();
                throw new RuntimeException(msg, var10);
            }
        } catch (Exception var12) {
            String msg = "Error getting property '" + name + "' from " + source.getClass();
            throw new RuntimeException(msg, var12);
        }
    } catch (Exception var14) {
        String msg = "Error getting property '" + name + "' from " + source.getClass();
        throw new RuntimeException(msg, var14);
    }
}

由於cache是初始化的HashMap物件,所以從catch中獲取不到任何快取方法,因此會呼叫source.getClass().getMethod(ClickUtils.toGetterName(name))方法。

1
2
3
4
5
6
7
public static String toGetterName(String property) {
    HtmlStringBuffer buffer = new HtmlStringBuffer(property.length() + 3);
    buffer.append("get");
    buffer.append(Character.toUpperCase(property.charAt(0)));
    buffer.append(property.substring(1));
    return buffer.toString();
}

此方法是為傳入的property屬性頭部新增"get"三個字元,並返回,因此回到getObjectPropertyValue方法,呼叫method.invoke(source)方法時,method引數值對應的是"get" + 傳入的name變數。在上述的分析中,name變數值是由Column#name屬性值決定的。因此控制Column#name屬性值,可以呼叫任意類中以"get"為首的無參方法。

 

對於Column#name屬性的控制也比較簡單,透過呼叫Column構造方法即可。

1
2
3
4
5
6
7
public Column(String name) {
    if (name == null) {
        throw new IllegalArgumentException("Null name parameter");
    } else {
        this.name = name;
    }
}

根據Commons-Collections2中,最終觸發RCE,還需要呼叫TemplatesImpl#newTransformer方法。而恰巧在TemplatesImpl#getOutputProperties方法中會呼叫到newTransformer方法,從而觸發自定義惡意類的初始化。

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
    try {
        return newTransformer().getOutputProperties();
    }
    catch (TransformerConfigurationException e) {
        return null;
    }
}

至此,利用鏈構造完成。

相關文章