Java學習之反射篇

Java學習之反射篇

0x00 前言

今天簡單來記錄一下,反射與註解的一些東西,反射這個機制對於後面的java反序列化漏洞研究和代碼審計也是比較重要。java

0x01 反射機制概述

Java反射是Java很是重要的動態特性,經過使用反射咱們不只能夠獲取到任何類的成員方法、成員變量、構造方法等信息,還能夠動態建立Java類實例、調用任意的類方法、修改任意的類成員變量值等。Java反射機制是Java語言的動態性的重要體現,也是Java的各類框架底層實現的靈魂。apache

0x02 Java反射

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:獲取方法名

使用getField方法獲取成員變量

咱們如今這裏編寫一個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);

    }

使用getDeclaredFields獲取全部成員變量

該方法不考慮修飾符

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

使用getDeclaredField獲取指定成員變量

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

獲取全部public修飾方法

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);      //調用方法




    }
}

若是咱們須要修改調用的方法或者說類,能夠直接在配置文件裏面進行修改,無需修改代碼。

0x03 反射調用Runtime

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的值,無需修改代碼就能夠執行其餘的命令了。

0x04 結尾

一邊調試代碼,一邊碼文章,寫完不知不覺已經5點了。仍是洗洗誰吧。

相關文章
相關標籤/搜索