Java单例---反射攻击破坏单例和解决方法

简介: Java单例---反射攻击破坏单例和解决方法

之前写过几篇单例的文章:

Java单例—双重锁校验详解

Java单例—序列化破坏单例模式原理解析

Java单例—静态内部类

在静态内部类中引出了反射攻击的问题,本篇就来说一下反射攻击,废话不多少说上代码:


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test1 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objClass = StaticInnerClass.class;
        //获取类的构造器
        Constructor constructor = objClass.getDeclaredConstructor();
        //把构造器私有权限放开
        constructor.setAccessible(true);
        //正常的获取实例方式
        StaticInnerClass staticInnerClass = StaticInnerClass.getInstance();
        //反射创建实例
        StaticInnerClass newStaticInnerClass = (StaticInnerClass) constructor.newInstance();
        System.out.println(staticInnerClass);
        System.out.println(newStaticInnerClass);
        System.out.println(staticInnerClass == newStaticInnerClass);
    }
}

上面这个代码的运行结果:

com.ygz.designpatterns.singleton.StaticInnerClass@4d7e1886

com.ygz.designpatterns.singleton.StaticInnerClass@3cd1a2f1

false

出现了两个不同的实例,这就违反了我们使用单例原则,不能保证只有一个实例,那么如何解决呢?还是直接上代码:


public class StaticInnerClass {
    private static class InnerClass{
        private static StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
    public static StaticInnerClass getInstance(){
        return InnerClass.staticInnerClass;
    }
    private StaticInnerClass(){
        //构造器判断,防止反射攻击,大家可以在下面这行if判断打断点来测试一下这个方法的过程,很好理解的
        if(InnerClass.staticInnerClass != null){
            throw new IllegalStateException();
        }
    }
}

这样写就可以防止反射攻击啦,这种方式同样适用于其他的饿汉模式防御反射攻击。但是如果不是在类加载的时候创建对象实例的这种单例,是没有办法防止反射攻击的,比如之前写过的那个双重锁校验,使用这种在构造器判断方式是无效的,来段代码证明一下,先上双重锁校验的构造器判断方式代码:


import java.io.Serializable;
/**
 * 双重锁校验的单例
 */
public class DoubleLock implements Serializable {
    public static volatile DoubleLock doubleLock = null;//volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取)
    private DoubleLock(){
        //构造器必须私有  不然直接new就可以创建
        //构造器判断,防止反射攻击
        if(doubleLock != null){
            throw new IllegalStateException();
        }
    }
    public static DoubleLock getInstance(){
        if(doubleLock == null){
            synchronized (DoubleLock.class){
                if(doubleLock == null){
                    doubleLock = new DoubleLock();
                }
            }
        }
        return doubleLock;
    }
    private Object readResolve(){
        return doubleLock;
    }
}

这段代码其他注释就不写了,感兴趣的可以直接去看文章开头的几篇博客。从这段代码可以看到我们采用同样的方式防止反射攻击,接下来我们测试一下他是不是可以防止反射攻击:


public class Test1 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objClass = DoubleLock.class;
        //获取类的构造器
        Constructor constructor = objClass.getDeclaredConstructor();
        //把构造器私有权限放开
        constructor.setAccessible(true);
        //反射创建实例   注意反射创建要放在前面,才会攻击成功,因为如果反射攻击在后面,先使用正常的方式创建实例的话,在构造器中判断是可以防止反射攻击、抛出异常的,
        //因为先使用正常的方式已经创建了实例,会进入if
        DoubleLock o1= (DoubleLock) constructor.newInstance();
        //正常的获取实例方式   正常的方式放在反射创建实例后面,这样当反射创建成功后,单例对象中的引用其实还是空的,反射攻击才能成功
        DoubleLock o2= DoubleLock.getInstance();
        System.out.println(o2);
        System.out.println(o1);
        System.out.println(o1 == o2);
    }
}

上面是测试代码,注意这段代码中要先进行反射创建实例,再进行正常的getInstance()创建实例,才能攻击成功,如果先getInstance()再反射创建,构造器中的判断是可以防止的,原因注释里面也有写,就不重复了。

还有一种尝试解决这种反射攻击的是:在单例里面加标识属性,如果实例化之后,标识改变,在构造器里面判断标识改变就抛异常,和上面这种气势差不多,但是没用的,反射可以把构造器的权限放开,同样可以把属性的权限放开,并且修改属性值,所以这种方式也是不行的,我还是写一下这个代码吧,方便大家理解,首先上加了标识属性的单例:


import java.io.Serializable;
/**
 * 双重锁校验的单例
 */
public class DoubleLock implements Serializable {
    public static volatile DoubleLock doubleLock = null;//volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取)
    private static boolean flag = true; //设置标识属性
    private DoubleLock(){
        if(flag){               //初始为true,进入构造器正常创建对象,然后把flag的值改成false,后面的就会抛异常
            flag = false;
        }else{
            throw new IllegalStateException();
        }
    }
    public static DoubleLock getInstance(){
        if(doubleLock == null){
            synchronized (DoubleLock.class){
                if(doubleLock == null){
                    doubleLock = new DoubleLock();
                }
            }
        }
        return doubleLock;
    }
    private Object readResolve(){
        return doubleLock;
    }
}

这段代码中加了一个flag属性用来标识是否已经创建了实例,接下来我们来通过反射破坏这个单例:


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test1 {
    public static void main(String[] args) throws Exception {
        Class objClass = DoubleLock.class;
        Constructor constructor = objClass.getDeclaredConstructor();
        //构造器权限
        constructor.setAccessible(true);
        //正常获取实例
        DoubleLock o1 = DoubleLock.getInstance();
        //获取到这个实例的flag属性
        Field flag = o1.getClass().getDeclaredField("flag");
        //属性权限放开
        flag.setAccessible(true);
        //属性值改为true
        flag.set(o1, true);
        DoubleLock o2 = (DoubleLock) constructor.newInstance();
        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o1 == o2);
    }
}

这段代码运行结果:

com.ygz.designpatterns.singleton.DoubleLock@2f0e140b

com.ygz.designpatterns.singleton.DoubleLock@7440e464

false

可以看到还是破坏了单例。

先写到这里,后面看有补充的继续补充

个人浅薄理解,欢迎补充



相关文章
|
1天前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
7天前
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
|
16天前
|
存储 安全 Java
【数据保护新纪元】Java编程:揭秘黑客攻击背后的防线,打造坚不可摧的安全堡垒!
【8月更文挑战第30天】本文全面介绍了Java安全性编程的基本概念和实战技巧,涵盖输入验证、错误处理、数据加密、权限控制及安全审计等方面。通过具体示例,帮助开发者有效预防安全风险,提升程序的稳定性和可靠性,保护用户数据安全。适合希望提升Java应用安全性的开发者参考。
28 4
|
1月前
|
设计模式 人工智能 Java
Java 如何使用单例类
Java 如何使用单例类
10 1
|
1月前
|
开发工具
java.lang.unsatisfiedlinkerror解决方法
java.lang.unsatisfiedlinkerror解决方法
98 1
|
2月前
|
安全 Java 测试技术
day26:Java零基础 - 反射
【7月更文挑战第26天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
29 5
|
25天前
|
缓存 安全 Java
【Java 第十篇章】反射
Java 反射技术让程序能在运行时动态获取类信息并操作对象,极大提升了灵活性与扩展性。本文将介绍反射的基本概念、原理及应用,包括如何使用 `Class`、`Field`、`Method` 和 `Constructor` 类进行动态操作。此外,还将探讨反射在动态加载、框架开发与代码测试中的应用场景,并提醒开发者注意性能与安全方面的问题,帮助你更合理地运用这一强大工具。
13 0
|
1月前
|
设计模式 Java
【Java】单例设计模式
【Java】单例设计模式
|
2月前
|
IDE Java 测试技术
Java进阶之反射
【7月更文挑战第14天】Java反射机制允许在运行时动态获取类信息、创建对象及调用其方法。它基于`Class`类,让我们能访问类的属性、方法、构造器。例如,通过`Class.forName()`加载类,`Class.newInstance()`创建对象,`Method.invoke()`执行方法。反射广泛应用于动态代理、单元测试、序列化及框架中,提供灵活性但牺牲了性能,且可破坏封装性。IDE的代码补全也是反射的应用之一。在使用时需谨慎,避免对私有成员的不当访问。
27 1
|
2月前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
43 1