JDK7u21
反序列化的关键
- 在于找到可以动态方法执行的代码:例如CC链中的Transformer,CB链中的PropertyUtils#getProperty
JDK7u21中动态方法执行的点,AnnotationInvocationHandler#equalsImpl中的hisValue = memberMethod.invoke(o)。- private Boolean equalsImpl(Object o) {
- if (o == this)
- return true;
- if (!type.isInstance(o))
- return false;
- for (Method memberMethod : getMemberMethods()) {
- String member = memberMethod.getName();
- Object ourValue = memberValues.get(member);
- Object hisValue = null;
- AnnotationInvocationHandler hisHandler = asOneOfUs(o);
- if (hisHandler != null) {
- hisValue = hisHandler.memberValues.get(member);
- } else {
- try {
- hisValue = memberMethod.invoke(o);
- } catch (InvocationTargetException e) {
- return false;
- } catch (IllegalAccessException e) {
- throw new AssertionError(e);
- }
- }
- if (!memberValueEquals(ourValue, hisValue))
- return false;
- }
- return true;
- }
- private Method[] getMemberMethods() {
- if (memberMethods == null) {
- memberMethods = AccessController.doPrivileged(
- new PrivilegedAction<Method[]>() {
- public Method[] run() {
- final Method[] mm = type.getDeclaredMethods();
- validateAnnotationMethods(mm);
- AccessibleObject.setAccessible(mm, true);
- return mm;
- }
- });
- }
- return memberMethods;
- }
复制代码 AnnotationInvocationHandler#equalsImpl是一个私有方法,仅在AnnotationInvocationHandler#invoke中被调用,它遍历执行了this.type的所有方法,如果这里的this.type为TemplatesImpl,那就可以实现任意代码执行。- public Object invoke(Object proxy, Method method, Object[] args) {
- String member = method.getName();
- Class<?>[] paramTypes = method.getParameterTypes();
- // Handle Object and Annotation methods
- if (member.equals("equals") && paramTypes.length == 1 &&
- paramTypes[0] == Object.class)
- return equalsImpl(args[0]);
- if (paramTypes.length != 0)
- throw new AssertionError("Too many parameters for an annotation method");
- switch(member) {
- case "toString":
- return toStringImpl();
- case "hashCode":
- return hashCodeImpl();
- case "annotationType":
- return type;
- }
- // Handle annotation member accessors
- Object result = memberValues.get(member);
- if (result == null)
- throw new IncompleteAnnotationException(type, member);
- if (result instanceof ExceptionProxy)
- throw ((ExceptionProxy) result).generateException();
- if (result.getClass().isArray() && Array.getLength(result) != 0)
- result = cloneArray(result);
- return result;
- }
复制代码 前面找到动态代码执行的关键后,构造链条的关键即在于如何调用equalsImpl和equalsImpl如何举行任意代码执行。
如何调用equalsImpl
由于AnnotationInvocationHandler实现了接口InvocationHandler,这里很明显可以采用CC1中用到的动态署理调用AnnotationInvocationHandler#invoke,但是注意到invoke的代码逻辑,我们署理的对象,必须是调用名为equals且只有一个Object类型的参数时,才会触发equalsImpl。
找到equals调用链
equals方法通常用于比力两个对象是否是同一引用,一个常见场景是集合set,集合是不允许重复对象的,所以在添加对象局势必涉及到比力操纵。- // HashSet#readObject
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- // Read in any hidden serialization magic
- s.defaultReadObject();
- // Read capacity and verify non-negative.
- int capacity = s.readInt();
- if (capacity < 0) {
- throw new InvalidObjectException("Illegal capacity: " +
- capacity);
- }
- // Read load factor and verify positive and non NaN.
- float loadFactor = s.readFloat();
- if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
- throw new InvalidObjectException("Illegal load factor: " +
- loadFactor);
- }
- // Read size and verify non-negative.
- int size = s.readInt();
- if (size < 0) {
- throw new InvalidObjectException("Illegal size: " +
- size);
- }
- // Set the capacity according to the size and load factor ensuring that
- // the HashMap is at least 25% full but clamping to maximum capacity.
- capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
- HashMap.MAXIMUM_CAPACITY);
- // Create backing HashMap
- map = (((HashSet<?>)this) instanceof LinkedHashSet ?
- new LinkedHashMap<E,Object>(capacity, loadFactor) :
- new HashMap<E,Object>(capacity, loadFactor));
- // Read in all elements in the proper order.
- for (int i=0; i<size; i++) {
- @SuppressWarnings("unchecked")
- E e = (E) s.readObject();
- map.put(e, PRESENT);
- }
- }
复制代码 这里k.hashCode()作为产生hash的唯一变量,由于TemplateImpl的hashCode方法是一个Native方法,我们欠好追溯,因此选择看一下署理对象的hashCode,而proxy.hashCode() 仍旧会调用到 AnnotationInvocationHandler#invoke ,进而调用到 AnnotationInvocationHandler#hashCodeImpl- // jdk8u21 hashmap
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);
- 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))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
复制代码 这里代码逻辑很简答,遍历 memberValues 这个Map中的每个key和value,计算每个 (127 * key.hashCode()) ^ value.hashCode() 并求和。
我们如何构造一个和恶意TemplateImpl的hash值一致的署理对象呢,JDK7u21中使用了一个非常巧妙的方法:
- 当 memberValues 中只有一个key和一个value时,该哈希简化成 (127 * key.hashCode()) ^ value.hashCode()
- 当 key.hashCode() 等于0时,任何数异或0的结果照旧他自己,所以该哈希简化成 value.hashCode() 。
- 当 value 就是TemplateImpl对象时,这两个哈希就酿成完全相等
- if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
复制代码 可以通过一个简单的爆破程序找到一个hash值为0的字符串
POC代码
- final int hash(Object k) {
- int h = 0;
- // 是否启用备用hash算法,一般情况下不启用,可忽略
- if (useAltHashing) {
- if (k instanceof String) {
- return sun.misc.Hashing.stringHash32((String) k);
- }
- h = hashSeed;
- }
- h ^= k.hashCode();
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
复制代码 注意这里代码添加的顺序- private int hashCodeImpl() {
- int result = 0;
- for (Map.Entry<String, Object> e : memberValues.entrySet()) {
- result += (127 * e.getKey().hashCode()) ^
- memberValueHashCode(e.getValue());
- }
- return result;
- }
- private static int memberValueHashCode(Object value) {
- Class<?> type = value.getClass();
- if (!type.isArray()) // primitive, string, class, enum const,
- // or annotation
- return value.hashCode();
- // ......
- }
复制代码 在hash表中,同一hash地址对应的链表中的元素是有先后顺序的,我们要包管key.equals(k)的k是恶意TemplatesImpl,key是署理对象,而不是反过来。
poc代码中的两个问题
有两个小问题,InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map); 1.为啥可以传递Templates.class进去,要求不是继续或实现了Annotation 接口的class吗 2.第一个参数传Templates.class是由于这个接口的实现类只有TemplatesImpl 这一个吗,后面type遍历也能调方法
解答:
<blockquote>
遐想到cc1,我当时也有这个疑问。后面看了一下,大概是和不同jdk版本的AnnotationInvocationHandler实现不同,我看了jdk7u21和jdk8u66这两个版本的构造函数是不一样的,7u21中AnnotationInvocationHandler(Class var1, Map var2) { this.type = var1; this.memberValues = var2; } 8u66中AnnotationInvocationHandler(Class type, Map memberValues) { Class[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); this.type = type; this.memberValues = memberValues; } 这里Class |