返回介绍

3 调用链

发布于 2024-09-16 15:35:01 字数 2702 浏览 0 评论 0 收藏 0

在比较 Java 对象时,常用到两种方法:

  • equals
  • compareTo

任意 Java 对象都拥有 equals 方法,它通常用于比较两个对象是否是同一个引用。另一个常见的会调用 equals 的场景就是集合 setset 中储存的对象不允许重复,所以在添加对象的时候,势必会涉及到比较操作。

HashSet 的 readObject 方法:

这里使用了一个 HashMap,将对象保存在 HashMap 的 key 处来做去重。

跟进 HashMap 的 put 方法:

变量 i 就是哈希值。两个不同的对象的 i 相等时,才会执行到 key.equals(k) ,触发前面说过的代码执行。

接下来的思路就是为了让 proxy 对象的哈希,等于 TemplateImpl 对象的哈希。

计算哈希的主要是下面这两行代码:

int hash = hash(key);
int i = indexFor(hash, table.length);

将其中的关键逻辑提取出来,可以得到下面这个函数:

public static int hash(Object key) {
    int h = 0;
    h ^= key.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    h = h ^ (h >>> 7) ^ (h >>> 4);
    return h & 15;
}

除了 key.hashCode() 外再没有其他变量,所以 proxy 对象与 TemplateImpl 对象的哈希是否相等,仅取决于这两个对象的 hashCode() 返回值是否相等。 TemplateImplhashCode() 是一个 Native 方法,每次运行都会发生变化,理论上是无法预测的,所以想让 proxyhashCode() 与之相等,只能通过 proxy.hashCode()

proxy.hashCode() 仍然会调用到 AnnotationInvocationHandler#invoke ,进而调用到 AnnotationInvocationHandler#hashCodeImpl ,跟进这个方法:

遍历这个 Map 中的每个 key 和 value,计算每个 (127 * key.hashCode()) ^ value.hashCode() 并求和。

JDK7u21 中使用了一个非常巧妙的方法:

  • memberValues 中只有一个 key 和一个 value 时,该哈希简化成 (127 * key.hashCode()) ^ value.hashCode()
  • key.hashCode()==0 时,任何数异或 0 的结果仍是它本身,所以该哈希简化成 value.hashCode()
  • value 就是 TemplateImpl 时,这两个哈希就变成完全相等

因此,通过寻找一个 hashCode 是 0 的对象作为的 key,将恶意 TemplateImpl 对象作为 value,这个 proxy 计算的 hashCode 就与 TemplateImpl 对象本身的 hashCode 相等了。

找一个 hashCode 是 0 的对象,通过一个简单的爆破程序来实现:

public static void bruteHashCode()
{
    for (long i = 0; i < 9999999999L; i++) {
        if (Long.toHexString(i).hashCode() == 0) {
            System.out.println(Long.toHexString(i));
        }
    }
}

第一个结果是 f5a5a608 ,这个也是 ysoserial 中用到的字符串。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文