设计模式之单例模式六(防反射攻击)

一、防反射攻击

在单例模式的设计过程中,我们说过很关键的一点是一定要将构造器设计为私有构造器,因为这样可以防止从外部构造对象。但是这样真的就会安全吗,答案肯定是不一定的。我们知道通过反射我们可以获取类中的域、方法、构造器,可以修改他们的访问权限,这样可以通过反射构造对象。如下所示:

上面的例子就是一个反射破坏的例子,针对的是恶汉式的单例模式,我们可以稍作修改防止这种反射攻击:

上面的修改从运行结果上来看是成功的,有人可能会说上面我们先通过getInstance方法构造了一个实例,然后再通过反射进行构造时就会检测到对象已经存在从而抛出异常,如果先通过反射构造,会不会结果不一样呢?其实结果是一样的,这里采用的是恶汉式的单例模式,即在类的初始化阶段会构造这个单例,想想java规范中引起类初始化的原因,我们就算先用反射构造,反射时如果类没有进行初始化,也会先执行类的初始化,然后再执行反射(java规范),此时单例对象仍然已经存在,也会抛出异常。

上面是对恶汉式单例模式的防反射攻击,之前我们还将过一种基于类加载延迟的懒汉式单例模式,由于其单例对象的构建都是在类初始化阶段,采取相同的方法也能做到防反射攻击:

我们再进一步思考,对于我们之前普通形势下的懒汉式单例模式可不可以通过上面的方式进行防反射攻击呢?下面我们来看一看:

上面的运行结果看似可行,其实是漏洞很大的,上面我们是先使用getInstance这个访问控制点进行的创建到单例对象,后面使用反射就会检测到对象已经存在,从而抛出异常,但是如果我们先使用反射进行构造单例对象呢?情况就不一样了,由于这种创建对象的过程不是在类的初始化阶段,故上面我们使用的方法时行不通的,交换构造过程执行结果如下:

所以说上面的方法时失败的,这种局限于构造顺序的防反射攻击是没有作用的,构造顺序是无法认为指定的,尤其是在多线程环境中完全由操作系统的调度决定。

试想:我们可不可以通过设置标志位的方式进行防反射攻击呢?下面我们就用实际的代码来尝试一下:

上面看似成功了,其实是不行的,我们可以通过反射访问构造器并修改构造器的访问权限进而创建对象,我们同样可以通过反射访问类的域并对域的权限进行修改进而修改域的值,只要我们通过反射创建对象后将标志位改为true,下次依然可以继续创建对象:

从上面的运行结果可以看出,我们实例化了两个对象,并且没有报异常,所以上面防反射攻击的做法就失败了。

二、Unsafe对象构造实例对单例模式的攻击破坏

上面对于恶汉式的单例模式我们防止了反射攻击,采用的基本思想是在私有构造方法中添加异常检查,但是我们还可以通过另一种方式构造只有私有构造器的类对象,那就是通过Unsafe对象的allocateInstance方法,不过这样做的前提是需要构造一个Unsafe类的对象,但是这个类也是一个只有私有构造器的类,其留出了一个公有的实例化接口getUnsafe,但是为了安全起见该接口设置了限制,并不是在任何类中都能调用,但是我们可以反射构造Unsafe的对象,再通过Unsafe对象来构造单例对象,Unsafe对象构造单例对象与反射不同的是它不会引起类的初始化,也不会执行类的构造函数,这样在构造函数中做的异常检查就没有作用了。导致防止不了这种攻击。

上面构造了两个实例,破坏了单例模式。

猜你喜欢

转载自blog.csdn.net/Wenlong_L/article/details/82811996