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);
}
}
}
再看看hash方法里面:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{new ConstantTransformer(Class.forName("java.lang.Runtime")),
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[]{Runtime.class, new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chainedTransformer= new ChainedTransformer(faketransformers);
public class Reflections {
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
setAccessible(field);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
Common-Collections 6
上一篇我们详细分析了
CommonsCollections1
这个利用链和其中的LazyMap
原理。但是我们说到,在Java 8u71
以后,这个利用链不能再利用了,主要原因是sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑变化了.但是在ysoserial
里,CC6
链算是比较通用的利用链了,主要就是高版本兼容。所以入口点还是
LazyMap#get()
这个地方,只是要解决Java高版本利用问 题,实际上就是在找是否还有其他调用LazyMap#get()
的地方TiedMapEntry
在
org.apache.commons.collections.keyvalue.TiedMapEntry
类中 ,里面的getValue
方法 中调用了this.map.get
,而其hashCode
方法调用了getValue
方法:其
getValue
方法调用了this.map.get
,hashCode
方法则调用了getValue
,所以要想触发LazyMap
利用链,就是要找到哪里调用了TiedMapEntry#hashCode
或者getValue
方法恰好在
java.util.HashMap#readObject
中是可以直接调用HashMap#hash()
,然后在hash
方法中再来调用TiedMapEntry#hashCode()
就行了但是在
ysoserial
中,是利用java.util.HashSet#readObject
到HashMap#put()
到HashMap#hash(key)
,最后到TiedMapEntry#hashCode()
。不过其实这样是比较麻烦的。先看看HashMap
源码:再看看hash方法里面:
所以只需要让
key
等于TiedMapEntry
对象即可。来看一下利用链:
构造一下poc:
这里学完后我回过来看觉得这里应该是弹出两次计算器的,但是只弹出了一次,因为
put
本来就会触发一次,反序列化后也应该触发一次,调试的时候把断点打到反序列化的地方也发现断不到,干脆打到exec
那里这里弹了一次,再次运行后就直接结束了,那说明没有到反序列化的地方,再回过头来看报错
这里报的是序列化的错,那么很可能序列化的时候程序就已经异常结束了。所以这样的写法,不太正确,还是接着看后面的写法。
ysoserial中的利用
接着我们来看一下
ysoserial
中是怎么利用的先看一下这里:
这串代码和之前的不同之处在于这里创建了一个假的
Transformer
数组,这样的好处其实之前弄cc1
唠嗑的时候有提到过,因为调试的时候会不经意触发tostring
方法,导致执行,这样的话可以防止这个,然后在最后生成payload
的时候再传入真的Transformer
数组,而这里是通过反射来传入真正的Transformer
数组的。来看一下反射的代码:
而在
ChainedTransformer
中存放Transformer
数组的变量名就是iTransformers
,所以说通过Reflections.setFieldValue(transformerChain, "iTransformers", transformers)
;就把真正的Transformer
数组设置进去了,或者也可以这样效果是一样的,都是通过反射把原来
iTransformers
里面的元素替换成了利用链这里为什么要用反射来赋值呢?
其实也很简单,因为
ChainedTransformer
里面的iTransformers
属性是被保护的,如果是public
才可以直接赋值现在这样看着一切都很正常,运行一下
运行是成功了,但是没有弹计算器
单步调试来看,到这里可以看到并没有进去
这里的
map.containsKey(key)
已经满足了,但是我们并没有事先传入键名啊,其实原因是在popMap.put(tiedMapEntry,"value");
中,HashMap
里的put
方法也会调用到hash(key)
:回忆一下
cc6
的链子,这链子是不是就已经连上了,相当于本地触发了;但我们第一次传入的是faketransformers
,这个时候super.map.containsKey(key)
是为false
的,是正常的,问题就是它进入了之后,执行了super.map.put()
操作,这里就把键名传入进去了,导致第二次,我们真正的transformers
传进来之后,利用反序列化触发时,它就为true
了,也就没办法触发了这里的解决方法也就很简单,只需要将这个Key,再从outerMap中移除即可:
成功弹出计算器