1. 程式人生 > 實用技巧 >Yii2反序列化(CVE-2020-15148)復現

Yii2反序列化(CVE-2020-15148)復現

反射

反射簡介

java的反射就是通過Class來在執行時獲取類的完整結構資訊 & 呼叫物件的方法。正常情況下,Java類在編譯前,就已經被載入到JVM中;而反射機制使得程式執行時還可以動態地去操作類的變數、方法等資訊。

反射的本質是當一個類被載入以後,Java虛擬機器就會自動產生一個 Class物件。通過這個 Class物件我們就能獲得載入到虛擬機器當中這個Class物件對應的方法、成員以及構造方法的宣告和定義等資訊。

優點是靈活在執行時才動態建立&獲取,缺點則是效率低,容易破壞類結構(因為繞過了原始碼容易干擾類原有內部邏輯)。

效率低主要是由以下幾個方面造成:

  • 反射呼叫過程中會產生大量的臨時物件,這些物件會佔用記憶體,可能會導致頻繁 gc,從而影響效能。
  • 反射呼叫方法時會從方法陣列中遍歷查詢,並且會檢查可見性等操作會耗時。
  • 反射呼叫時編譯器難以對動態呼叫的程式碼提前做優化。
  • 反射一般會涉及自動裝箱/拆箱和型別轉換,都會帶來一定的資源開銷

反射使用

大致分為以下幾個方面:

  • 獲取Constructor類物件
 <-- 1. 獲取類的建構函式(傳入建構函式的引數型別)->>
   // a. 獲取指定的建構函式 (公共 / 繼承)
   Constructor<T> getConstructor(Class<?>... parameterTypes)
   // b. 獲取所有的建構函式(公共 / 繼承) 
   Constructor<?>[] getConstructors(); 
   // c. 獲取指定的建構函式 ( 不包括繼承)
   Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 
   // d. 獲取所有的建構函式( 不包括繼承)
   Constructor<?>[] getDeclaredConstructors(); 
 // 最終都是獲得一個Constructor類物件
 
 // 特別注意:
   // 1. 不帶 "Declared"的方法支援取出包括繼承、公有(Public) & 不包括有(Private)的建構函式
   // 2. 帶 "Declared"的方法是支援取出包括公共(Public)、保護(Protected)、預設(包)訪問和私有(Private)的構造方法,但不包括繼承的建構函式
   // 下面同理
  • 獲取Method類物件
<--  2. 獲取類的屬性(傳入屬性名) -->
  // a. 獲取指定的屬性(公共 / 繼承)
   Field getField(String name) ;
  // b. 獲取所有的屬性(公共 / 繼承)
   Field[] getFields() ;
  // c. 獲取指定的所有屬性 (不包括繼承)
   Field getDeclaredField(String name) ;
  // d. 獲取所有的所有屬性 (不包括繼承)
   Field[] getDeclaredFields() ;
// 最終都是獲得一個Field類物件
  • 獲取Field類物件
<-- 3. 獲取類的方法(傳入方法名 & 引數型別)-->
  // a. 獲取指定的方法(公共 / 繼承)
    Method getMethod(String name, Class<?>... parameterTypes) ;
  // b. 獲取所有的方法(公共 / 繼承)
   Method[] getMethods() ;
  // c. 獲取指定的方法 ( 不包括繼承)
   Method getDeclaredMethod(String name, Class<?>... parameterTypes) ;
  // d. 獲取所有的方法( 不包括繼承)
   Method[] getDeclaredMethods() ;
// 最終都是獲得一個Method類物件
  • 其他常用方法
getSuperclass(); 
// 返回父類

String getName(); 
// 作用:返回完整的類名(含包名,如java.lang.String ) 
 
Object newInstance(); 

總結來說分為帶 "Declared"和不帶 "Declared",不帶Declared的方法只能獲取公有屬性以及從父類繼承的,帶Declared的則只能訪問自己定義的屬性(但是可以獲取私有屬性)。還有可以根據是否提供引數分類,帶引數的方法一般是根據引數匹配符合的屬性,不帶引數的獲取方法會返回所有符合的屬性。

當獲取到Constructor、Method、Field之後我們可以根據獲取的物件做進一步的獲取資訊或操作

<-- 1. 通過Constructor 類物件獲取類建構函式資訊 -->
  String getName();// 獲取構造器名
  Class getDeclaringClass();// 獲取一個用於描述類中定義的構造器的Class物件
  int getModifiers();// 返回整型數值,用不同的位開關描述訪問修飾符的使用狀況
  Class[] getExceptionTypes();// 獲取描述方法丟擲的異常型別的Class物件陣列
  Class[] getParameterTypes();// 獲取一個用於描述引數型別的Class物件陣列

<-- 2. 通過Field類物件獲取類屬性資訊 -->
  String getName();// 返回屬性的名稱
  Class getDeclaringClass(); // 獲取屬性型別的Class型別物件
  Class getType();// 獲取屬性型別的Class型別物件
  int getModifiers(); // 返回整型數值,用不同的位開關描述訪問修飾符的使用狀況
  Object get(Object obj) ;// 返回指定物件上 此屬性的值
  void set(Object obj, Object value) // 設定 指定物件上此屬性的值為value
 
<-- 3. 通過Method 類物件獲取類方法資訊 -->
  String getName();// 獲取方法名
  Class getDeclaringClass();// 獲取方法的Class物件 
  int getModifiers();// 返回整型數值,用不同的位開關描述訪問修飾符的使用狀況
  Class[] getExceptionTypes();// 獲取用於描述方法丟擲的異常型別的Class物件陣列
  Class[] getParameterTypes();// 獲取一個用於描述引數型別的Class物件陣列

<--額外:java.lang.reflect.Modifier類 -->
// 作用:獲取訪問修飾符

static String toString(int modifiers)   
// 獲取對應modifiers位設定的修飾符的字串表示

static boolean isXXX(int modifiers) 
// 檢測方法名中對應的修飾符在modifiers中的值

陣列與列舉的反射

//建立元素型別、元素長度指定的陣列
public static Object newInstance(Class<?> componentType, int length)
//建立多維度的陣列,dimensions可連續傳遞多個,分別代表不同維度
public static Object newInstance(Class<?> componentType, int... dimensions)
//獲取指定陣列的對應索引的值
public static native Object get(Object array, int index)
//賦值給指定陣列的對應索引下的值
public static native void set(Object array, int index, Object value)
//獲取陣列長度
public static native int getLength(Object array)


//獲取當前列舉型別Class的所有定義的列舉常量
public T[] getEnumConstants()

String[] strArr = new String[10];
int[][] twoDimArr = new int[3][2];
int[] oneDimArr = new int[10];
Class<? extends String[]> strArrCls = strArr.getClass();
Class<? extends int[][]> twoDimArrCls = twoDimArr.getClass();
Class<? extends int[]> oneDimArrCls = oneDimArr.getClass();

獲取基本資料型別的Class

//基本型別的class即為對應的包裝型別
Class<Integer> intCls = int.class;
Class<Byte> byteCls = byte.class;
Class<Character> charCls = char.class;
Class<Double> doubleCls = double.class;
//void也是一種特殊的型別,返回的也是對應的包裝類Void
Class<Void> voidCls = void.class;

反射應用

反射是Android原始碼裡面應用最多的地方,小到xml的解析,大到dex檔案的載入,application的啟動,均大量運用了反射。

  • 反射生成物件、執行方法、獲取類資訊
  • 工廠模式優化
  • 動態代理

Java 反射真的很慢嗎?

詳解Java反射操作

反射技術在Android中的應用

Java反射:這是一份全面 & 詳細的 Java反射機制 學習指南

一篇文章帶你學懂Java反射

大家都說 Java 反射效率低,你知道原因在哪裡麼