今天簡單來記錄一下,反射與註解的一些東西,反射這個機制對於後面的java反序列化漏洞研究和代碼審計也是比較重要。java
Java反射是Java很是重要的動態特性,經過使用反射咱們不只能夠獲取到任何類的成員方法、成員變量、構造方法等信息,還能夠動態建立Java類實例、調用任意的類方法、修改任意的類成員變量值等。Java反射機制是Java語言的動態性的重要體現,也是Java的各類框架底層實現的靈魂。apache
Java反射操做的是java.lang.Class對象,因此咱們須要要先獲取到Class對象。安全
獲取Class對象的方式:框架
1. Class.forName("全類名"):將字節碼文件加載進內存,返回Class對象 多用於配置文件,將類名定義在配置文件中。讀取文件,加載類 2. 類名.class:經過類名的屬性class獲取 多用於參數的傳遞 3. 對象.getClass():getClass()方法在Object類中定義着。 多用於對象的獲取字節碼的方式
代碼實例:jsp
方式一: Class cls1 = Class.forName("Domain.Person"); System.out.println(cls1); 方式二: Class cls2 = Person.class; System.out.println(cls2); 方式三: Person p = new Person(); Class cls3 = p.getClass(); System.out.println(cls3);
同一個字節碼文件(*.class)在一次程序運行過程當中,只會被加載一次,不論經過哪種方式獲取的Class對象都是同一個。ide
class類方法:函數
獲取成員變量方法:學習
1. 獲取成員變量們 * Field[] getFields() :獲取全部public修飾的成員變量 * Field getField(String name) 獲取指定名稱的 public修飾的成員變量 * Field[] getDeclaredFields() 獲取全部的成員變量,不考慮修飾符 * Field getDeclaredField(String name)
獲取構造方法:this
* Constructor<?>[] getConstructors() * Constructor<T> getConstructor(類<?>... parameterTypes) * Constructor<T> getDeclaredConstructor(類<?>... parameterTypes) * Constructor<?>[] getDeclaredConstructors()
獲取成員方法:3d
* Method[] getMethods() * Method getMethod(String name, 類<?>... parameterTypes) * Method[] getDeclaredMethods() * Method getDeclaredMethod(String name, 類<?>... parameterTypes)
獲取全類名:
String getName()
成員變量設置:
Field:成員變量 * 操做: 1. 設置值 * void set(Object obj, Object value) 2. 獲取值 * get(Object obj) 3. 忽略訪問權限修飾符的安全檢查 * setAccessible(true):暴力反射
構造方法:
建立對象: * T newInstance(Object... initargs) * 若是使用空參數構造方法建立對象,操做能夠簡化:Class對象的newInstance方法
方法對象:
執行方法: * Object invoke(Object obj, Object... args) * 獲取方法名稱: * String getName:獲取方法名
咱們如今這裏編寫一個person類。
person代碼:
package Domain; public class Person { private String name ; private int age; public String a ; public Person() { } public void eat(){ System.out.println("eat"); } public void eat(String food){ System.out.println("eat "+food); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", a='" + a + '\'' + '}'; } public Person(String name, int age) { this.name = name; this.age = age; } }
main類代碼:
public static void main(String[] args) throws Exception { Class cls = Class.forName("Domain.Person"); Field a = cls.getField("a"); //獲取成員a變量 Person person = new Person(); Object o = a.get(person); //獲取成員變量的值 System.out.println(o); a.set(person,"abc"); //修改爲員a變量的值爲abc System.out.println(person); }
該方法不考慮修飾符
public static void main(String[] args) throws Exception { Class cls = Class.forName("Domain.Person"); System.out.println(person); Field[] declaredFields = cls.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); }
Class cls = Class.forName("Domain.Person"); Field b = cls.getDeclaredField("b"); b.setAccessible(true); //使用暴力反射機制,忽略訪問權限修飾符的安全檢測 Person person = new Person(); Object o1 = b.get(person); System.out.println(o1);
這裏person該類中的成員變量是被private修飾的,咱們想要訪問他的值必須使用暴力反射,暴力反射
能夠,忽略訪問權限修飾符的安全檢測。
Class cls = Class.forName("Domain.Person"); Constructor constructor = cls.getConstructor(String.class,int.class);//獲取構造器 System.out.println(constructor); //有參構造 Object o = constructor.newInstance("123", 18); //建立對象 System.out.println(o); //無參構造 Object o1 = constructor.newInstance(); System.out.println(o1);
Class cls = Class.forName("Domain.Person"); //無參數方法 Method eat = cls.getMethod("eat"); Person person = new Person(); eat.invoke(person); //調用eat方法 //有參數方法 Method eat1 = cls.getMethod("eat", String.class); //獲取eat方法而且設置參數 eat1.invoke(person,"fish");
Class cls = Class.forName("Domain.Person"); Method[] methods = cls.getMethods(); for (Method method : methods) { System.out.println(method); }
Class cls = Class.forName("Domain.Person"); String name = cls.getName(); System.out.println(name);
前面這些只是簡單的一些方法的使用,後面來看一個小案例來了解反射的具體應用。
步驟
1.首先咱們須要建立一個配置文件,而後定義須要建立的兌現和須要執行的方法定義在配置文件裏面。
2.在程序中讀取配置文件
3.使用反射機制加載類文件進內存
4.建立對象
5.執行方法
通常java裏面的配置文件都是以.properites結尾,那麼就定義一個pro.properites文件。
pro.properites文件內容:
className=Domain.Person //寫入須要加載的類 methodName=eat //寫入須要加載的方法
main類裏面內容:
public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { Properties properties = new Properties(); //建立properties對象 ClassLoader classLoader = Person.class.getClassLoader(); //獲取加載 InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properites"); //獲取路徑文件流 properties.load(resourceAsStream); //加載文件 //獲取配置文件定義的數據 String className = properties.getProperty("className"); //獲取類名 String methodName = properties.getProperty("methodName");//獲取方法名 Class cls = Class.forName(className); //將類加載進內存 Object o = cls.newInstance(); //建立無參構造對象 Method method = cls.getMethod(methodName); //建立方法 method.invoke(o); //調用方法 } }
若是咱們須要修改調用的方法或者說類,能夠直接在配置文件裏面進行修改,無需修改代碼。
Runtime這個函數有exec方法能夠本地執行命令,大部分關於jsp命令執行的payload可能都是調用Runtime進行Runtime的exec方法進行命令執行的。
package com; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; public class Test { public static void main(String[] args) throws IOException { InputStream ipconfig = Runtime.getRuntime().exec("ipconfig").getInputStream(); String s = IOUtils.toString(ipconfig,"gbk"); //使用IOUtils.toString靜態方法將字節輸入流轉換爲字符 System.out.println(s); } }
這樣的代碼基本都是固定死的,若是要屢次傳入參數執行命令的話,這樣的寫法確定是不行的,那麼這時候就能夠用到反射。
package com; import org.apache.commons.io.IOUtils; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class Test2 { public static void main(String[] args) throws Exception { String command = "ipconfig"; Class cls = Class.forName("java.lang.Runtime"); //Runtime加載進內存 Constructor declaredConstructor = cls.getDeclaredConstructor(); //獲取構造方法 declaredConstructor.setAccessible(true); //暴力反射 Object o = declaredConstructor.newInstance(); //建立Runtime類 Method exec = cls.getMethod("exec", String.class); //獲取exec方法,設置須要參數string類型參數 Process process = (Process) exec.invoke(o,command); //執行exec方法,並傳入ipconfig參數 // System.out.println(process); InputStream inputStream = process.getInputStream(); //獲取輸出的數據 String ipconfig = IOUtils.toString(inputStream,"gbk"); //字節輸出流轉換爲字符 System.out.println(ipconfig); } }
這時候只須要修改command的值,無需修改代碼就能夠執行其餘的命令了。
一邊調試代碼,一邊碼文章,寫完不知不覺已經5點了。仍是洗洗誰吧。