‘click1 gadget’构造思路是基于’Commons-Collections2’的Sink点’(com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl)’和source点’(java.util.PriorityQueue)’。’Commons-Collect…
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
方法。
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)
方法
public Object getProperty(Object row) {
return this.getProperty(this.getName(), row);
}
调用this.getName()
方法获取Column#name
属性值,并调用this.getProperty(name , row)
方法。
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)
方法。
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
方法时,作为参数传入。
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))
方法。
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
构造方法即可。
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
方法,从而触发自定义恶意类的初始化。
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
至此,利用链构造完成。
- 本文作者: 深信服千里目安全实验室
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/870
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!