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; } } |
至此,利用链构造完成。