这是代码审计知识星球中《Java安全漫谈》的第十二篇文章。
本文带大家编写一个简化版的CommonsCollections6利用链,代码量相比于ysoserial减少50%,能够让大家更好理解。
上一篇文章我们详细分析了CommonsCollections1这个利用链和其中的LazyMap原理。但是我们说到,在Java 8u71以后,这个利用链不能再利用了,主要原因是sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑变化了。
在ysoserial中,CommonsCollections6可以说是commons-collections这个库中相对比较通用的利用链,为了解决高版本Java的利用问题,我们先来看看这个利用链。
不过,本文我不会按照ysoserial中的代码进行讲解,原因是ysoserial的代码过于复杂了,而且其实用到了一些没必要的类。
我们先看下我这条简化版利用链:
/*
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()
的那一部分,因为LazyMap#get
后面的部分在上一篇文章里已经说了。所以简单来说,解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用LazyMap#get()
的地方。
我们找到的类是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 int hashCode() {
Object value = this.getValue();
return (this.getKey()