Java安全基礎(chǔ)之Java的反射機(jī)制
好長(zhǎng)時(shí)間沒(méi)有更新了,今天更新一篇關(guān)于java反射機(jī)制的文章,初學(xué)Java安全,內(nèi)容如有不恰當(dāng)?shù)牡胤剑€請(qǐng)各位大佬指正。
一、什么是反射
反射(Reflection)是Java的特征之一,C/C++語(yǔ)言中不存在反射,反射的存在使得運(yùn)行中的Java程序能夠獲取自身的信息,并且可以操作類或?qū)ο蟮膬?nèi)部屬性。那什么是反射呢?
下面是官方的解釋:
反射使得Java代碼能夠發(fā)現(xiàn)有已加載類的字段、方法和構(gòu)造函數(shù)的信息,并在安全限制內(nèi)使用反射的字段、方法和構(gòu)造函數(shù)對(duì)其底層對(duì)應(yīng)的對(duì)象進(jìn)行操作
簡(jiǎn)單來(lái)說(shuō),通過(guò)反射,我們可以在運(yùn)行時(shí)獲得程序或程序集中每一個(gè)類型的成員和成員的信息。同樣的,Java的反射機(jī)制也是也是如此,在運(yùn)行狀態(tài)中,通過(guò)Java的反射機(jī)制,我們能夠判斷一個(gè)對(duì)象所屬的類;了解任意一個(gè)類的所有屬性和方法;能夠調(diào)用任意一個(gè)對(duì)象的任意方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為Java語(yǔ)言的反射機(jī)制。
二、反射的用途
在靜態(tài)語(yǔ)言中,一般對(duì)象的類型都是在編譯期就確定下來(lái)的,二通過(guò)Java反射機(jī)制,可以動(dòng)態(tài)的創(chuàng)建對(duì)象并調(diào)用其方法或?qū)傩裕@也就使得的反射的用途很廣泛,在開(kāi)發(fā)過(guò)程中使用Eclipse、IDEA等開(kāi)發(fā)工具時(shí),當(dāng)我們輸入一個(gè)對(duì)象或類并想調(diào)用它的屬性或方法時(shí),編譯器會(huì)自動(dòng)列出它的屬性或方法,這是通過(guò)反射實(shí)現(xiàn)的;載入,JavaBean和jsp之間的調(diào)用也是通過(guò)反射實(shí)現(xiàn)的。反射最重要的用途是開(kāi)發(fā)各種框架,如上文中提到的Spring框架以及ORM框架,都是通過(guò)反射機(jī)制來(lái)實(shí)現(xiàn)的。
面向不同的用戶,反射機(jī)制的重要程度也大不相同。對(duì)于框架開(kāi)發(fā)人員來(lái)說(shuō),反射雖小但作用非常大,它是各種容器實(shí)現(xiàn)的核心。對(duì)于一般的開(kāi)發(fā)者來(lái)說(shuō),反射的作用相對(duì)較小。但總體來(lái)說(shuō),適當(dāng)了解二框架的底層機(jī)制對(duì)我們的編程思想也是非常有幫助的。
三、靜態(tài)語(yǔ)言和動(dòng)態(tài)語(yǔ)言
在學(xué)習(xí)反射之前,我們有必要了解一下什么是動(dòng)態(tài)語(yǔ)言和靜態(tài)語(yǔ)言
- 靜態(tài)語(yǔ)言(強(qiáng)類型語(yǔ)言)
靜態(tài)語(yǔ)言是在編譯時(shí)變量的數(shù)據(jù)類型即可確定的語(yǔ)言,多數(shù)靜態(tài)語(yǔ)言要求在使用變量之前必須聲明數(shù)據(jù)的類型。如C++、Java、Delphi、C#等
- 動(dòng)態(tài)語(yǔ)言(弱類型語(yǔ)言)
動(dòng)態(tài)語(yǔ)言時(shí)在運(yùn)行是確定數(shù)據(jù)類型的語(yǔ)言。變量使用之前不需要類型聲明,通常變量的類型是被賦值的那個(gè)值的類型。如PHP/ASP/Ruby/Python.Perl/ABAP/SQL/JavaScript/Unix Shell等等。
可以在程序運(yùn)行時(shí)改變程序結(jié)構(gòu)和變量類型的語(yǔ)言,比如在程序運(yùn)行時(shí),新的類和對(duì)象可以被加載和創(chuàng)建,新的函數(shù)或方法可以被加入或者去除等等。
3.1、動(dòng)態(tài)特性
動(dòng)態(tài)語(yǔ)言具有的某些特性即為動(dòng)態(tài)特性
以PHP舉例,一段代碼,其中變量值的改變可鞥導(dǎo)致這段代碼發(fā)生功能上的變化,我們將這種現(xiàn)象稱為PHP的動(dòng)態(tài)特性
比如下面的這個(gè)例子
我們只有當(dāng)代碼運(yùn)行時(shí),通過(guò)變量傳入的值才能確定其具體功能
3.2、動(dòng)態(tài)特性與Java反射
正是因?yàn)镻HP中存在多種動(dòng)態(tài)特性,使得開(kāi)發(fā)人員能通過(guò)很少的代碼實(shí)現(xiàn)非常多的功能,比較經(jīng)典的例子就是一句話木馬,通過(guò)一行<?php @eval($_POST[cmd]);代碼即可實(shí)現(xiàn)多種多樣的功能
但是Java本身是一門靜態(tài)語(yǔ)言,無(wú)法像PHP那么靈活多變。但是通過(guò)Java反射機(jī)制,可以為自身提供一些動(dòng)態(tài)特性。比如,當(dāng)我們通過(guò)IDE寫代碼時(shí),敲擊點(diǎn)好號(hào)“.”,會(huì)出現(xiàn)當(dāng)前對(duì)象或類所包含的屬性和方法,這里用到的就是Java反射機(jī)制。
反射最重要的用途就是開(kāi)發(fā)各種通用框架,很多框架嗾使通過(guò)XML文件來(lái)進(jìn)行配置的(如:struts.xml,spring-*.xml等),即所謂的框架核心配置文件。為了確??蚣艿耐ㄓ眯?,程序運(yùn)行時(shí)需要根據(jù)配置文件中對(duì)應(yīng)的內(nèi)容加載不同的類或?qū)ο?,調(diào)用不同的方法,這也依賴于Java反射機(jī)制。
3.3、Java反射機(jī)制功能點(diǎn)
綜上所述,Java反射機(jī)制的功能可分為如下幾點(diǎn):
- 在程序運(yùn)行時(shí)查找一個(gè)對(duì)象所屬的類
- 在程序運(yùn)行時(shí)查找任意一個(gè)類的成員變量和方法
- 在程序運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象
- 在程序運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法
四、Java的命令執(zhí)行類
4.1、java.lang.Runtime類
這個(gè)類是一個(gè)共有類,每個(gè)Java應(yīng)用程序都有一個(gè)Runtime類實(shí)例,它允許應(yīng)用程序與運(yùn)行應(yīng)用程序的環(huán)境交互。當(dāng)前運(yùn)行時(shí)可以從getRuntime方法獲得。應(yīng)用程序無(wú)法創(chuàng)建自己的此類實(shí)例。
該類的主要方法是:getRuntime(),得到一個(gè)和當(dāng)前程序相關(guān)聯(lián)的Runtime類的對(duì)象,解釋如下:
返回與當(dāng)前Java應(yīng)用關(guān)聯(lián)的runtime對(duì)象。大多數(shù)Runtime類的方法是實(shí)例方法,所以必須被當(dāng)前運(yùn)行時(shí)對(duì)象調(diào)用。
Runtime對(duì)象可以調(diào)用exec()方法執(zhí)行命令,詳細(xì)文檔解釋如下:
在一個(gè)單獨(dú)的進(jìn)程中執(zhí)行指定的命令。這是一個(gè)方便的方法。以exec(command)形式調(diào)用與exec(String,Stringp[],file)的表現(xiàn)是相同的。
下面是一段代碼示例:
五、獲取類對(duì)象
獲取類對(duì)象的方式有很多種,這里提供四種方式
- forName()
- 直接獲取
- getClass()
- getSystemClassLoader().loadClass()
5.1、獲取類對(duì)象-forName()
如果要使用Class類中的方法完成,就需要使用forName方法,只要有類名稱即可,更為防爆,擴(kuò)展性更強(qiáng)。這種方法并不陌生,在配置JDBC的時(shí)候,我們通常采用這種方法
5.2、獲取類對(duì)象-直接獲取
任何數(shù)據(jù)類型都具備一個(gè)靜態(tài)的屬性,可以使用.class來(lái)獲取其對(duì)應(yīng)的Class對(duì)象。這種方法相對(duì)簡(jiǎn)單,但還是要明確用到類的靜態(tài)成員
5.3、獲取類對(duì)象-getClass()
我們可以通過(guò)Object類中的getClass()方法來(lái)獲取字節(jié)碼對(duì)象,不過(guò)這種方式較為繁瑣,必須要明確具體的類,然后創(chuàng)建對(duì)象
六、獲取類方法
獲取某個(gè)Class對(duì)象的方法集合,主要有以下幾種方法:
- getDeclareMethods
- getDecleardMethod
- getMethods
- getMethod
6.1、獲取類方法-getDeclaredMethods
getDeclaredMethods方法返回類或接口聲明的所有方法,包括:public、protected、private和默認(rèn)方法,但不包括繼承的方法
6.2、獲取類方法-getMethods
getMethods方法返回某個(gè)類的所有public方法,包括其繼承的public方法
6.3、獲取類方法-getMethod
getMethod方法只能返回一個(gè)特定的方法,如 Runtime類中的exec()方法,該方法的第一個(gè)參數(shù)為方法名稱,后面的參數(shù)為方法的參數(shù)對(duì)應(yīng)Class的對(duì)象
6.4、獲取類方法 - getDeclaredMethod
getDeclaredMethod方法與getMethod類似,也只能返回一個(gè)特定的方法,該方法的第一個(gè)參數(shù)為方法名,第二個(gè)參數(shù)名是方法參數(shù)
七、獲取類成員變量
為了更直觀地體現(xiàn)出獲取類成員變量的方法,我們首先創(chuàng)建一個(gè)Student類,要獲取Student類成員變量,主要有以下幾個(gè)方法:
- getDeclaredFields
- getDeclaredField
- getFields
- getField
7.1、獲取類成員變量-getDeclaredFields
getDeclaredFields方法能夠獲得類成員變量數(shù)組,包括public、private和proteced,但是不包括父類的聲明字段
7.2、獲取類成員變量-getFields
gteFields能夠獲得某個(gè)類的所有的public字段,包括父類中的字段
7.3、獲取類成員變量-getDeclaredField
該方法與getDeclaresFields的區(qū)別是只能獲得類的單個(gè)成員變量
7.4、獲取類成員變量-getField
與getFields類似,getField方法能夠獲得某個(gè)類特定的public字段,包括父類中的字段
八、構(gòu)造任意類的對(duì)象
構(gòu)造任意類的對(duì)象最經(jīng)典的方式:
無(wú)參數(shù):className.newInstance()
有參數(shù):getConstructor().newInstance()
構(gòu)造任意類的對(duì)象兩種方式異同
(1)首先兩種方式在原始碼所在的位置上不同
- Class.newInstance() → 在java.lang包
- Constructor.newInstance() → 在java.lang.reflect包
(2)在使用方法上的不同
- ClassName.newInstance():
- getConstructor().newInstance()
- Class.newInstance() 只能反射無(wú)參的構(gòu)造器
- Constructor.newInstance() 可以反射任何構(gòu)造器
- Class.newInstance() 需要構(gòu)造器可見(jiàn)
- Constructor.newInstance() 可以反私有構(gòu)造器
- Class.newInstance() 對(duì)于捕獲或者未捕獲的一場(chǎng)均由構(gòu)造器丟擲;
- Constructor.newInstance() 通常會(huì)把丟擲的異常封裝成InvocationTrageException丟擲
九、調(diào)用任意實(shí)例對(duì)象的方法
調(diào)用任意實(shí)例對(duì)象的方法最經(jīng)典的方式
- 直接調(diào)用:objectName.functionName()
- invoke調(diào)用:invoke()
invoke方法調(diào)用任意實(shí)例對(duì)象是通過(guò)反射實(shí)現(xiàn)的,下面是一個(gè)示例:
十、不安全的反射
如前所述,利用Java的反射機(jī)制,我們可以無(wú)視類方法、變量訪問(wèn)權(quán)限修飾符,調(diào)用任何類的任意方法、訪問(wèn)并修改成員變量值,但是這樣做可能導(dǎo)致安全問(wèn)題,如果一個(gè)攻擊者能夠通過(guò)應(yīng)用程序創(chuàng)建意外的控制流路徑,就有可能繞過(guò)安全檢查發(fā)起相關(guān)攻擊。假設(shè)有一段代碼如下:
其中存在一個(gè)字段name,當(dāng)獲取用戶請(qǐng)求的name字段后進(jìn)行判斷,如果請(qǐng)求的是Delect操作,則執(zhí)行DelectCommand函數(shù);若執(zhí)行的是Add操作,則執(zhí)行AddCommand函數(shù);如果不是這兩種操作,則執(zhí)行其他代碼。
假如開(kāi)發(fā)者看到了這段代碼,他認(rèn)為可以使用Java的反射來(lái)重構(gòu)此代碼以減少代碼行,如下所示:
這樣的重構(gòu)看起來(lái)使得代碼行減少,消除了if/else塊,而且可以在不修改命令分派器的情況下添加新的命令類型,但是如果沒(méi)有對(duì)傳入的name字段進(jìn)行限制,則可以實(shí)例化實(shí)現(xiàn)Command接口的任何對(duì)象,從而導(dǎo)致安全問(wèn)題。實(shí)際上,攻擊者甚至不局限于本例中的Command接對(duì)象,而是使用任何其他對(duì)象來(lái)實(shí)現(xiàn),如調(diào)用系統(tǒng)中任何對(duì)象的默認(rèn)構(gòu)造函數(shù),或者調(diào)用Runtime對(duì)象去執(zhí)行系統(tǒng)命令,這可能導(dǎo)致遠(yuǎn)程命令執(zhí)行漏洞,因此不安全的反射的危害性極大,也是我們審計(jì)過(guò)程中需要重點(diǎn)關(guān)注的內(nèi)容。