1. 程式人生 > >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); } }

上面這個程式碼的執行結果:
[email protected]
[email protected]
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);

    }

}

這段程式碼執行結果:
[email protected]
[email protected]
false

可以看到還是破壞了單例。
先寫到這裡,後面看有補充的繼續補充
國際慣例加上群連結:個人淺薄理解,歡迎補充
點選連結加入群聊【Java技術學習閒聊群】:https://jq.qq.com/?_wv=1027&k=59emCBA

相關推薦

Java---反射攻擊解決方法

import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Test1 { public static

java web專案中遇到的問題解決方法

1.怎麼解決tomcat閃退 tomcat啟動批處理startup.bat最上邊加這兩句 SET JAVA_HOME=D:\Java\jdk1.8.0_101 SET TOMCAT_HOME=D:\Program Files\Apache Software Foundati

一個JAVA模式的典型錯誤應用的分析解決方法

                問題來自論壇,其程式碼如下:[java] view plain copy print?import java.sql.Connection;  import java.sql.PreparedStatement;  import java.sql.ResultSet;  imp

【譯】7. Java反射——私有欄位私有方法

 ===========================================================================================      儘管普遍的觀點是不能直接訪問私有欄位和私有方法的,實際上通過Java反射是可以訪問其他類的私有欄位和私有方法

Java中的反射(reflection)代理(proxy)

Java中的反射和代理 特別宣告:本文主要是記錄了學習反射和代理的學習過程,代理部分的大段文字均屬轉載。您可以閱讀原文。除此之外,所有程式碼及反射部分均屬個人所寫。 反射 最近剛讀了一遍《Java程式設計思想》第四版中第14章——型別資訊,獲益匪淺,摘

Android Studio出現java.util.concurrent.ExecutionException: com.android.ide.common.process.ProcessException的總結解決方法

logo ide roc for deb execution 所有 不同類 util 1. Error:Execution failed for task ‘mergeDebugAndroidTestResources‘.  > Error: java.util.c

java為什麽要重寫hashCodeequals方法

有時 不同 遞歸 步驟 原生 下標 set .com 底層 如果不被重寫(原生)的hashCode和equals是什麽樣的? 不被重寫(原生)的hashCode值是根據內存地址換算出來的一個值。 不被重寫(原生)的equals方法是嚴格判斷一個對象

JAVA中的反射中加載類的方法

ace getpass over this test AS demo1 str tcl 反射:加載類的方法有三種, 1.用Class.forName("類名")方法來調用; 2.類名.class得到 3.用對象.getClass()得到 package com.ma.re

java面試題之Thread的run()start()方法有什麼區別

run()方法:   是在主執行緒中執行方法,和呼叫普通方法一樣;(按順序執行,同步執行) start()方法:   是建立了新的執行緒,在新的執行緒中執行;(非同步執行)   public class App { public static void main( Stri

java Swing 彈出新對話方塊的方法,以及關閉新對話方塊遇到的問題解決方法

GIFShow 是JFrame的一個繼承類。 public class GIFShow extends JFrame {} 在另一個視窗中的button事件中,實現             &

【夾娃系列】java面試基礎知識儲備(¥2)——JVM記憶體劃分記憶體溢位異常的原因解決方法

JVM記憶體劃分和記憶體溢位 JVM記憶體劃分 記憶體溢位的異常和解決辦法 JVM記憶體劃分 堆:存放物件例項,被所有的執行緒共享的一塊區域。垃圾收集器管理的主要區域。 方法區:儲存虛擬機器載入的類資訊,常量,靜態變

Python中json.loads()無法解析引號字串問題的兩種解決方法

目錄 1、json檔案的儲存與載入 2、json.loads()無法解析單引號字串問題 3、解決方案 方案一:替換單引號 方案二:在使用json.loads()前使用eval()和json.dumps()進行處理 1、json檔案的儲存與載入 一般來說,我建立字典、儲

關於vscode更新後 格式化程式碼造成函式括號後的空格被刪除,引號變雙引號問題的解決方法

前段時間做專案時遇到了語法格式的警告即 究其原因是因為專案建立時選擇了 ESLint 來規範程式碼,由於在VSCode1.7.2中替換了內建格式化外掛。所以在新的專案中格式化程式碼後引發程式碼規範驗證錯誤,經過幾番研究之後終於將其解決下面貼出解決方案: 修改Vscode的配置檔案,不知

java 泛型的型別擦除方法

oracle原文地址:https://docs.oracle.com/javase/tutorial/java/generics/erasure.html  在Java中,泛型的引入是為了在編譯時提供強型別檢查和支援泛型程式設計。為了實現泛型,Java編譯器應用型別擦除實現:   &n

java】--泛型-型別擦除與多型的衝突解決方法

型別擦除與多型的衝突和解決方法 現在有這樣一個泛型類: [java] view plain copy print ? class Pair<T>&

Java ConcurrentModificationException異常原因解決方法

  在前面一篇文章中提到,對Vector、ArrayList在迭代的時候如果同時對其進行修改就會丟擲java.util.ConcurrentModificationException異常。下面我們就來討論以下這個異常出現的原因以及解決辦法。 一.Concur

java 中物件的 一對一關係 (封裝構造方法)

java 中物件的 一對一關係 簡單介紹: … java中物件的對應關係有很多種,比如單向一對一,雙向一對一,一對多,多對一,多對多等,其實現原理相同,接下來,我們詳解一對一關係。 說明: … 所

java nio 系列教程(1)----buffer介紹使用方法

大家推薦個靠譜的公眾號程式設計師探索之路,大家一起加油 ​   package com.zzh.buffer; import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; /** * 一.緩衝區(b

Java 虛擬機器記憶體溢位問題解決方法

一什麼是記憶體溢位 1記憶體溢位是指應用系統中存在無法回收的記憶體或使用的記憶體過多,最終使得程式執行要用到的記憶體大於虛擬機器能提供的最大記憶體。 2 Java的記憶體管理就是物件的分配和釋放問題。 在Java中,記憶體的分配是由程式完成的,而記憶體的釋

關於聯想拯救者 安裝Ubuntu18.04系統時遇到坑的通用解決方法

一:安裝前的準備        1.下載refus,用於製作U盤啟動器 (哪裡下載?百度搜索refus,我用的是3.1版本)        2.下載ubuntu18.04系統(同樣百度搜索下載即可) 二:製作U盤啟動器               重點就是分割槽型別和新增對舊