程式設計師面試系列之Java單例模式的攻擊與防禦

i042416發表於2018-09-17

我寫的程式設計師面試系列

Java面試系列-webapp資料夾和WebContent資料夾的區別?

程式設計師面試系列:Spring MVC能響應HTTP請求的原因?

Java程式設計師面試系列-什麼是Java Marker Interface(標記介面)

使用JDK自帶的工具jstack找出造成執行程式死鎖的原因

程式設計面試題:編寫一個會造成資料庫死鎖的應用

JavaScript面試系列:JavaScript設計模式之橋接模式和懶載入

使用JavaScript ES6的新特性計算Fibonacci(非波拉契數列)

單例模式在很多Java程式設計師的眼中,應該是設計模式裡最簡單的一種了。那麼單例模式可能會被攻擊,您聽說過麼?

程式設計師面試系列之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;
      }
}

程式設計師面試系列之Java單例模式的攻擊與防禦

上面是一個餓漢式單例。

程式設計師面試系列之Java單例模式的攻擊與防禦

然而我只需要將這個單例類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);

程式設計師面試系列之Java單例模式的攻擊與防禦

第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");
      }
  }
}
}

程式設計師面試系列之Java單例模式的攻擊與防禦

這種防禦措施無法從根本上杜絕Singleton被攻擊,因為攻擊者仍舊可以透過反射來修改布林變數flag的值,從而繞過這個檢查。

最理想的不會受到攻擊的單例模式實現是藉助Java裡列舉類Enumeration的特性:

程式設計師面試系列之Java單例模式的攻擊與防禦

這種實現型別的單例模式的消費程式碼:

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單例模式的攻擊與防禦

究其原因,是因為現在我們是透過Java列舉方式實現的單例,列舉類沒有傳統意義上的建構函式,因此對這種反射攻擊免疫。

要獲取更多Jerry的原創技術文章,請關注公眾號"汪子熙"或者掃描下面二維碼:


程式設計師面試系列之Java單例模式的攻擊與防禦

程式設計師面試系列之Java單例模式的攻擊與防禦


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24475491/viewspace-2214340/,如需轉載,請註明出處,否則將追究法律責任。

相關文章