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