程序员面试系列之Java单例模式的攻击与防御
我寫的程序員面試系列
Java面試系列-webapp文件夾和WebContent文件夾的區別?
程序員面試系列:Spring MVC能響應HTTP請求的原因?
Java程序員面試系列-什么是Java Marker Interface(標記接口)
使用JDK自帶的工具jstack找出造成運行程序死鎖的原因
編程面試題:編寫一個會造成數據庫死鎖的應用
JavaScript面試系列:JavaScript設計模式之橋接模式和懶加載
使用JavaScript ES6的新特性計算Fibonacci(非波拉契數列)
單例模式在很多Java程序員的眼中,應該是設計模式里最簡單的一種了。那么單例模式可能會被攻擊,您聽說過么?
說到“單例模式被攻擊”這個話題,大家最容易想到的可能就是通過序列化/反序列化來攻擊單例模式,因為一個對象實例序列化再反序列化后,得到的新的對象雖然各字段內容和原字段一致,然而對象地址和原始對象地址相比已經發生了變化,因此它們是兩個不同的對象。
上面的結論完全正確,然而除了序列化/反序列化,單例模式還可能遭受另一種方式的攻擊,即反射攻擊(Reflection attack)。
看一個具體例子:
public class JerrySingleton {private String name;private JerrySingleton(){name = "Jerry";}private static class SingletonHolder{private static final JerrySingleton INSTANCE = new JerrySingleton();}public static JerrySingleton getInstance() {return SingletonHolder.INSTANCE;}}上面是一個餓漢式單例。
然而我只需要將這個單例類JerrySingleton的構造函數通過反射設置成可以訪問Accessible,然后就能通過反射調用該構造函數,進而生成新的對象實例。這樣就破壞了單例模式。
Class<?> classType = JerrySingleton.class;Constructor<?> c = classType.getDeclaredConstructor(null);c.setAccessible(true);JerrySingleton e1 = (JerrySingleton)c.newInstance();JerrySingleton e2 = JerrySingleton.getInstance();System.out.println(e1 == e2);第6行代碼會打印false。
針對這種攻擊,一種可行的防御措施是在單例類的構造函數內定義一個布爾變量,初始化為false。當構造函數執行后,該變量被置為true。如果接下來構造函數再次被執行,則人為拋出異常,避免構造函數重復執行。
public class JerrySingletonImproved {private static boolean flag = false;private JerrySingletonImproved(){synchronized(JerrySingletonImproved.class) {if(flag == false) {flag = !flag;}else {throw new RuntimeException("Singleton violated");}}}}這種防御措施無法從根本上杜絕Singleton被攻擊,因為攻擊者仍舊可以通過反射來修改布爾變量flag的值,從而繞過這個檢查。
最理想的不會受到攻擊的單例模式實現是借助Java里枚舉類Enumeration的特性:
這種實現類型的單例模式的消費代碼:
System.out.println(“Name:” + JerrySingletonAnotherApproach.INSTANCE.getName());
如果攻擊者通過前面介紹的反射代碼對這種實現方式的單例進行攻擊,JDK會拋出NoSuchMethodException異常:
Exception in thread "main" java.lang.NoSuchMethodException: singleton.JerrySingletonAnotherApproach.<init>()at java.lang.Class.getConstructor0(Class.java:3082)at java.lang.Class.getDeclaredConstructor(Class.java:2178)at singleton.SingletonAttack.test3(SingletonAttack.java:31)at singleton.SingletonAttack.main(SingletonAttack.java:43)究其原因,是因為現在我們是通過Java枚舉方式實現的單例,枚舉類沒有傳統意義上的構造函數,因此對這種反射攻擊免疫。
要獲取更多Jerry的原創技術文章,請關注公眾號”汪子熙”或者掃描下面二維碼:
總結
以上是生活随笔為你收集整理的程序员面试系列之Java单例模式的攻击与防御的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Eclipse被卡死了或者失去响应了后分
- 下一篇: MongoDB最简单的入门教程之三 使用