先登陸 ,通過您注冊密保的手機號碼和手機驗證碼進行解鎖游戲帳號,進入游戲。為了保證您驗證碼的安全性,在登錄密保網(wǎng)站后,我們將動態(tài)生成新的驗證碼在頁面顯示給您,以免您在登錄時輸入的驗證碼被惡意程序獲取。 但是登陸之后只能解鎖,不能取消綁定。由于每次 生成新的驗證碼 ,一不小心關(guān)了當(dāng)時的網(wǎng)頁,驗證碼也找不到了 之后只能用第2種方法解鎖:如果您的手機驗證碼也忘記了,可以通過游戲數(shù)據(jù)取回帳號,取消游戲帳號與密保的綁定關(guān)系?!? 貌似這個是要清空安全等等信息,不過我不需要這樣,只要取消手機密保就可以了,帳號的安全資料我都知道,也不需要改,客服說7個工作日內(nèi)給結(jié)果,請教有相關(guān)經(jīng)驗的朋友,還要傳真身份證復(fù)印件什么的嗎,是不是帳號的安全資料都清空。如果真那么麻煩,貌似我去電信申請要回以前的號碼還簡單點。 有誰知道的,麻煩告訴下,真有點暈了
創(chuàng)新互聯(lián)網(wǎng)站建設(shè)服務(wù)商,為中小企業(yè)提供做網(wǎng)站、成都網(wǎng)站建設(shè)服務(wù),網(wǎng)站設(shè)計,成都網(wǎng)站托管等一站式綜合服務(wù)型公司,專業(yè)打造企業(yè)形象網(wǎng)站,讓您在眾多競爭對手中脫穎而出創(chuàng)新互聯(lián)。
以下從技術(shù)角度就常見的保護措施 和常用工具來看看如何有效保護java代碼:1. 將java包裝成exe 特點:將jar包裝成可執(zhí)行文件,便于使用,但對java程序沒有任何保護。不要以為生成了exe就和普通可執(zhí)行文件效果一樣了。這些包裝成exe的程序運行時都會將jar文件釋放到臨時目錄,很容易獲取。常用的工具有exe4j、jsmooth、NativeJ等等。jsmooth生成的exe運行時臨時目錄在exe所在目錄中或是用戶臨時目錄 中;exe4j生成的exe運行時臨時目錄在用戶臨時目錄中;NativeJ生成的exe直接用winrar打開,然后用zip格式修復(fù)成一個jar文件,就得到了原文件。如果只是為了使用和發(fā)布方便,不需要保護java代碼,使用這些工具是很好的選擇。2. java混淆器特點:使用一種或多種處理方式將class文件、java源代碼進行混淆處理后生成新的class,使混淆后的代碼不易被反編譯,而反編譯后的代碼難以閱 讀和理解。這類混淆器工具很多,而且也很有成效。缺點:雖然混淆的代碼反編譯后不易讀懂,但對于有經(jīng)驗的人或是多花些時間,還是能找到或計算出你代碼中隱藏的敏感內(nèi)容,而且在很多應(yīng)用中不是全部代碼都能混淆的,往往一些關(guān)鍵的庫、類名、方法名、變量名等因使用要求的限制反而還不能混淆。3. 隔離java程序到服務(wù)端特點:把java程序放到服務(wù)端,讓用戶不能訪問到class文件和相關(guān)配套文件,客戶端只通過接口訪問。這種方式在客戶/服務(wù)模式的應(yīng)用中能較好地保護java代碼。缺點是:必須是客戶/服務(wù)模式,這種特點限制了此種方式的使用范圍;客戶端因為邏輯的暴露始終是較為薄弱的環(huán)節(jié),所以訪問接口時一般都需要安全性認證。4. java加密保護特點:自定義ClassLoader,將class文件和相關(guān)文件加密,運行時由此ClassLoader解密相關(guān)文件并裝載類,要起到保護作用必須自定 義本地代碼執(zhí)行器將自定義ClassLoader和加密解密的相關(guān)類和配套文件也保護起來。此種方式能很有效地保護java代碼。缺點:可以通過替換JRE包中與類裝載相關(guān)的java類或虛擬機動態(tài)庫截獲java字節(jié)碼。 jar2exe屬于這類工具。5. 提前編譯技術(shù)(AOT) 特點:將java代碼靜態(tài)編譯成本地機器碼,脫離通用JRE。此種方式能夠非常有效地保護java代碼,且程序啟動比通用JVM快一點。具有代表性的是GNU的gcj,可以做到對java代碼完全提前編譯,但gcj存在諸多局限性,如:對JRE 5不能完整支持、不支持JRE 6及以后的版本。由于java平臺的復(fù)雜性,做到能及時支持最新java版本和JRE的完全提前編譯是非常困難的,所以這類工具往往采取靈活方式,該用即時編譯的地方還是 要用,成為提前編譯和即時編譯的混合體。缺點:由于與通用JRE的差異和java運用中的復(fù)雜性,并非java程序中的所有jar都能得到完全的保護;只能使用此種工具提供的一個運行環(huán)境,如果工具更新滯后或你需要特定版本的JRE,有可能得不到此種工具的支持。 Excelsior JET屬于這類工具。6. 使用jni方式保護特點:將敏感的方法和數(shù)據(jù)通過jni方式處理。此種方式和“隔離java程序到服務(wù)端”有些類似,可以看作把需要保護的代碼和數(shù)據(jù)“隔離”到動態(tài)庫中,不同的是可以在單機程序中運用。缺點和上述“隔離java程序到服務(wù)端”類似。7. 不脫離JRE的綜合方式保護特點:非提前編譯,不脫離JRE,采用多種軟保護方式,從多方面防止java程序被竊取。此種方式由于采取了多種保護措施,比如自定義執(zhí)行器和裝載器、加密、JNI、安全性檢測、生成可執(zhí)行文件等等,使保護力度大大增強,同樣能夠非常有效地保護java代碼。缺點:由于jar文件存在方式的改變和java運用中的復(fù)雜性,并非java程序中的所有jar都能得到完全的保護;很有可能并不支持所有的JRE版本。 JXMaker屬于此類工具。8. 用加密鎖硬件保護特點:使用與硬件相關(guān)的專用程序?qū)ava虛擬機啟動程序加殼,將虛擬機配套文件和java程序加密,啟動的是加殼程序,由加殼程序建立一個與硬件相關(guān)的 受保護的運行環(huán)境,為了加強安全性可以和加密鎖內(nèi)植入的程序互動。此種方式與以上“不脫離JRE的綜合方式保護”相似,只是使用了專用硬件設(shè)備,也能很好地保護java代碼。缺點:有人認為加密鎖用戶使用上不太方便,且每個安裝需要附帶一個。從以上描述中我們可以看出:1. 各種保護方式都有其優(yōu)缺點,應(yīng)根據(jù)實際選用2. 要更好地保護java代碼應(yīng)該使用綜合的保護措施3. 單機環(huán)境中要真正有效保護java代碼,必須要有本地代碼程序配合當(dāng)然,安全都是相對的,一方面看你的保護措施和使用的工具能達到的程度,一方面看黑客的意愿和能力,不能只從技術(shù)上保護知識產(chǎn)權(quán)??傊?,在java 代碼保護方面可以采取各種可能的方式,不可拘泥于那些條條框框。
在本文中,我們討論了對付 13 種不同靜態(tài)暴露的技巧。對于每種暴露,我們解釋了不處理這些安全性問題所造成的影響。我們還為您推薦了一些準(zhǔn)則,要開發(fā)不受這些靜態(tài)安全性暴露威脅的、健壯且安全的 Java 應(yīng)用程序,您應(yīng)該遵循這些準(zhǔn)則。一有合適的時機,我們就提供代碼樣本(既有暴露的代碼也有無暴露的代碼)。
對付高嚴重性暴露的技巧
請遵循下列建議以避免高嚴重性靜態(tài)安全性暴露:
限制對變量的訪問
讓每個類和方法都成為 final,除非有足夠的理由不這樣做
不要依賴包作用域
使類不可克隆
使類不可序列化
使類不可逆序列化
避免硬編碼敏感數(shù)據(jù)
查找惡意代碼
限制對變量的訪問
如果將變量聲明為 public,那么外部代碼就可以操作該變量。這可能會導(dǎo)致安全性暴露。
影響
如果實例變量為 public,那么就可以在類實例上直接訪問和操作該實例變量。將實例變量聲明為 protected 并不一定能解決這一問題:雖然不可能直接在類實例基礎(chǔ)上訪問這樣的變量,但仍然可以從派生類訪問這個變量。
清單 1 演示了帶有 public 變量的代碼,因為變量為 public 的,所以它暴露了。
清單 1. 帶有 public 變量的代碼
class Test {
public int id;
protected String name;
Test(){
id = 1;
name = "hello world";
}
//code
}
public class MyClass extends Test{
public void methodIllegalSet(String name){
this.name = name; // this should not be allowed
}
public static void main(String[] args){
Test obj = new Test();
obj.id = 123; // this should not be allowed
MyClass mc = new MyClass();
mc.methodIllegalSet("Illegal Set Value");
}
}
建議
一般來說,應(yīng)該使用取值方法而不是 public 變量。按照具體問題具體對待的原則,在確定哪些變量特別重要因而應(yīng)該聲明為 private 時,請將編碼的方便程度及成本同安全性需要加以比較。清單 2 演示了以下列方式來使之安全的代碼:
清單 2. 不帶有 public 變量的代碼
class Test {
private int id;
private String name;
Test(){
id = 1;
name = "hello world";
}
public void setId(int id){
this.id = id;
}
public void setName(String name){
this.name = name;
}
public int getId(){
return id;
}
public String getName(){
return name;
}
}
讓每個類和方法都為 final
不允許擴展的類和方法應(yīng)該聲明為 final。這樣做防止了系統(tǒng)外的代碼擴展類并修改類的行為。
影響
僅僅將類聲明為非 public 并不能防止攻擊者擴展類,因為仍然可以從它自己的包內(nèi)訪問該類。
建議
讓每個類和方法都成為 final,除非有足夠的理由不這樣做。按此建議,我們要求您放棄可擴展性,雖然它是使用諸如 Java 語言之類的面向?qū)ο笳Z言的主要優(yōu)點之一。在試圖提供安全性時,可擴展性卻成了您的敵人;可擴展性只會為攻擊者提供更多給您帶來麻煩的方法。
不要依賴包作用域
沒有顯式地標(biāo)注為 public、private 或 protected 的類、方法和變量在它們自己的包內(nèi)是可訪問的。
影響
如果 Java 包不是封閉的,那么攻擊者就可以向包內(nèi)引入新類并使用該新類來訪問您想保護的內(nèi)容。諸如 java.lang 之類的一些包缺省是封閉的,一些 JVM 也讓您封閉自己的包。然而,您最好假定包是不封閉的。
建議
從軟件工程觀點來看,包作用域具有重要意義,因為它可以阻止對您想隱藏的內(nèi)容進行偶然的、無意中的訪問。但不要依靠它來獲取安全性。應(yīng)該將類、方法和變量顯式標(biāo)注為 public、private 或 protected 中適合您特定需求的那種。
使類不可克隆
克隆允許繞過構(gòu)造器而輕易地復(fù)制類實例。
影響
即使您沒有有意使類可克隆,外部源仍然可以定義您的類的子類,并使該子類實現(xiàn) java.lang.Cloneable。這就讓攻擊者創(chuàng)建了您的類的新實例??截惉F(xiàn)有對象的內(nèi)存映象生成了新的實例;雖然這樣做有時候是生成新對象的可接受方法,但是大多數(shù)時候是不可接受的。清單 3 說明了因為可克隆而暴露的代碼:
清單 3. 可克隆代碼
class MyClass{
private int id;
private String name;
public MyClass(){
id=1;
name="HaryPorter";
}
public MyClass(int id,String name){
this.id=id;
this.name=name;
}
public void display(){
System.out.println("Id ="+id+"
"+"Name="+name);
}
}
// hackers code to clone the user class
public class Hacker extends MyClass implements Cloneable {
public static void main(String[] args){
Hacker hack=new Hacker();
try{
MyClass o=(MyClass)hack.clone();
o.display();
}
catch(CloneNotSupportedException e){
e.printStackTrace();
}
}
}
建議
要防止類被克隆,可以將清單 4 中所示的方法添加到您的類中:
清單 4. 使您的代碼不可克隆
public final Object clone()
throws java.lang.CloneNotSupportedException{
throw new java.lang.CloneNotSupportedException();
}
如果想讓您的類可克隆并且您已經(jīng)考慮了這一選擇的后果,那么您仍然可以保護您的類。要做到這一點,請在您的類中定義一個為 final 的克隆方法,并讓它依賴于您的一個超類中的一個非 final 克隆方法,如清單 5 中所示:
清單 5. 以安全的方式使您的代碼可克隆
public final Object clone()
throws java.lang.CloneNotSupportedException {
super.clone();
}
類中出現(xiàn) clone() 方法防止攻擊者重新定義您的 clone 方法。
使類不可序列化
序列化允許將類實例中的數(shù)據(jù)保存在外部文件中。闖入代碼可以克隆或復(fù)制實例,然后對它進行序列化。
影響
序列化是令人擔(dān)憂的,因為它允許外部源獲取對您的對象的內(nèi)部狀態(tài)的控制。這一外部源可以將您的對象之一序列化成攻擊者隨后可以讀取的字節(jié)數(shù)組,這使得攻擊者可以完全審查您的對象的內(nèi)部狀態(tài),包括您標(biāo)記為 private 的任何字段。它也允許攻擊者訪問您引用的任何對象的內(nèi)部狀態(tài)。
建議
要防止類中的對象被序列化,請在類中定義清單 6 中的 writeObject() 方法:
清單 6. 防止對象序列化
private final void writeObject(ObjectOutputStream out)
throws java.io.NotSerializableException {
throw new java.io.NotSerializableException("This object cannot
be serialized");
}
通過將 writeObject() 方法聲明為 final,防止了攻擊者覆蓋該方法。
使類不可逆序列化
通過使用逆序列化,攻擊者可以用外部數(shù)據(jù)或字節(jié)流來實例化類。
影響
不管類是否可以序列化,都可以對它進行逆序列化。外部源可以創(chuàng)建逆序列化成類實例的字節(jié)序列。這種可能為您帶來了大量風(fēng)險,因為您不能控制逆序列化對象的狀態(tài)。請將逆序列化作為您的對象的另一種公共構(gòu)造器 — 一種您無法控制的構(gòu)造器。
建議
要防止對對象的逆序列化,應(yīng)該在您的類中定義清單 7 中的 readObject() 方法:
清單 7. 防止對象逆序列化
private final void readObject(ObjectInputStream in)
throws java.io.NotSerializableException {
throw new java.io.NotSerializableException("This object cannot
be deserialized");
}
通過將該方法聲明為 final,防止了攻擊者覆蓋該方法。
避免硬編碼敏感數(shù)據(jù)
您可能會嘗試將諸如加密密鑰之類的秘密存放在您的應(yīng)用程序或庫的代碼。對于你們開發(fā)人員來說,這樣做通常會把事情變得更簡單。
影響
任何運行您的代碼的人都可以完全訪問以這種方法存儲的秘密。沒有什么東西可以防止心懷叵測的程序員或虛擬機窺探您的代碼并了解其秘密。
建議
可以以一種只可被您解密的方式將秘密存儲在您代碼中。在這種情形下,秘密只在于您的代碼所使用的算法。這樣做沒有多大壞處,但不要洋洋得意,認為這樣做提供了牢固的保護。您可以遮掩您的源代碼或字節(jié)碼 — 也就是,以一種為了解密必須知道加密格式的方法對源代碼或字節(jié)碼進行加密 — 但攻擊者極有可能能夠推斷出加密格式,對遮掩的代碼進行逆向工程從而揭露其秘密。
這一問題的一種可能解決方案是:將敏感數(shù)據(jù)保存在屬性文件中,無論什么時候需要這些數(shù)據(jù),都可以從該文件讀取。如果數(shù)據(jù)極其敏感,那么在訪問屬性文件時,您的應(yīng)用程序應(yīng)該使用一些加密/解密技術(shù)。
查找惡意代碼
從事某個項目的某個心懷叵測的開發(fā)人員可能故意引入易受攻擊的代碼,打算日后利用它。這樣的代碼在初始化時可能會啟動一個后臺進程,該進程可以為闖入者開后門。它也可以更改一些敏感數(shù)據(jù)。
這樣的惡意代碼有三類:
類中的 main 方法
定義過且未使用的方法
注釋中的死代碼
影響
入口點程序可能很危險而且有惡意。通常,Java 開發(fā)人員往往在其類中編寫 main() 方法,這有助于測試單個類的功能。當(dāng)類從測試轉(zhuǎn)移到生產(chǎn)環(huán)境時,帶有 main() 方法的類就成為了對應(yīng)用程序的潛在威脅,因為闖入者將它們用作入口點。
請檢查代碼中是否有未使用的方法出現(xiàn)。這些方法在測試期間將會通過所有的安全檢查,因為在代碼中不調(diào)用它們 — 但它們可能含有硬編碼在它們內(nèi)部的敏感數(shù)據(jù)(雖然是測試數(shù)據(jù))。引入一小段代碼的攻擊者隨后可能調(diào)用這樣的方法。
避免最終應(yīng)用程序中的死代碼(注釋內(nèi)的代碼)。如果闖入者去掉了對這樣的代碼的注釋,那么代碼可能會影響系統(tǒng)的功能性。
可以在清單 8 中看到所有三種類型的惡意代碼的示例:
清單 8. 潛在惡意的 Java 代碼
public void unusedMethod(){
// code written to harm the system
}
public void usedMethod(){
//unusedMethod(); //code in comment put with bad intentions,
//might affect the system if uncommented
// int x = 100;
// x=x+10; //Code in comment, might affect the
//functionality of the system if uncommented
}
建議
應(yīng)該將(除啟動應(yīng)用程序的 main() 方法之外的)main() 方法、未使用的方法以及死代碼從應(yīng)用程序代碼中除去。在軟件交付使用之前,主要開發(fā)人員應(yīng)該對敏感應(yīng)用程序進行一次全面的代碼評審。應(yīng)該使用“Stub”或“dummy”類代替 main() 方法以測試應(yīng)用程序的功能。
對付中等嚴重性暴露的技巧
請遵循下列建議以避免中等嚴重性靜態(tài)安全性暴露:
不要依賴初始化
不要通過名稱來比較類
不要使用內(nèi)部類
不要依賴初始化
您可以不運行構(gòu)造器而分配對象。這些對象使用起來不安全,因為它們不是通過構(gòu)造器初始化的。
影響
在初始化時驗證對象確保了數(shù)據(jù)的完整性。
例如,請想象為客戶創(chuàng)建新帳戶的 Account 對象。只有在 Account 期初余額大于 0 時,才可以開設(shè)新帳戶。可以在構(gòu)造器里執(zhí)行這樣的驗證。有些人未執(zhí)行構(gòu)造器而創(chuàng)建 Account 對象,他可能創(chuàng)建了一個具有一些負值的新帳戶,這樣會使系統(tǒng)不一致,容易受到進一步的干預(yù)。
建議
在使用對象之前,請檢查對象的初始化過程。要做到這一點,每個類都應(yīng)該有一個在構(gòu)造器中設(shè)置的私有布爾標(biāo)志,如清單 9 中的類所示。在每個非 static 方法中,代碼在任何進一步執(zhí)行之前都應(yīng)該檢查該標(biāo)志的值。如果該標(biāo)志的值為 true,那么控制應(yīng)該進一步繼續(xù);否則,控制應(yīng)該拋出一個例外并停止執(zhí)行。那些從構(gòu)造器調(diào)用的方法將不會檢查初始化的變量,因為在調(diào)用方法時沒有設(shè)置標(biāo)志。因為這些方法并不檢查標(biāo)志,所以應(yīng)該將它們聲明為 private 以防止用戶直接訪問它們。
清單 9. 使用布爾標(biāo)志以檢查初始化過程
public class MyClass{
private boolean initialized = false;
//Other variables
public MyClass (){
//variable initialization
method1();
initialized = true;
}
private void method1(){ //no need to check for initialization variable
//code
}
public void method2(){
try{
if(initialized==true){
//proceed with the business logic
}
else{
throw new Exception("Illegal State Of the object");
}
}catch(Exception e){
e.printStackTrace();
}
}
}
如果對象由逆序列化進行初始化,那么上面討論的驗證機制將難以奏效,因為在該過程中并不調(diào)用構(gòu)造器。在這種情況下,類應(yīng)該實現(xiàn) ObjectInputValidation 接口:
清單 10. 實現(xiàn) ObjectInputValidation
interface java.io.ObjectInputValidation {
public void validateObject() throws InvalidObjectException;
}
所有驗證都應(yīng)該在 validateObject() 方法中執(zhí)行。對象還必須調(diào)用 ObjectInputStream.RegisterValidation() 方法以為逆序列化對象之后的驗證進行注冊。 RegisterValidation() 的第一個參數(shù)是實現(xiàn) validateObject() 的對象,通常是對對象自身的引用。注:任何實現(xiàn) validateObject() 的對象都可能充當(dāng)對象驗證器,但對象通常驗證它自己對其它對象的引用。RegisterValidation() 的第二個參數(shù)是一個確定回調(diào)順序的整數(shù)優(yōu)先級,優(yōu)先級數(shù)字大的比優(yōu)先級數(shù)字小的先回調(diào)。同一優(yōu)先級內(nèi)的回調(diào)順序則不確定。
當(dāng)對象已逆序列化時,ObjectInputStream 按照從高到低的優(yōu)先級順序調(diào)用每個已注冊對象上的 validateObject()。
不要通過名稱來比較類
有時候,您可能需要比較兩個對象的類,以確定它們是否相同;或者,您可能想看看某個對象是否是某個特定類的實例。因為 JVM 可能包括多個具有相同名稱的類(具有相同名稱但卻在不同包內(nèi)的類),所以您不應(yīng)該根據(jù)名稱來比較類。
影響
如果根據(jù)名稱來比較類,您可能無意中將您不希望授予別人的權(quán)利授予了闖入者的類,因為闖入者可以定義與您的類同名的類。
例如,請假設(shè)您想確定某個對象是否是類 com.bar.Foo 的實例。清單 11 演示了完成這一任務(wù)的錯誤方法:
清單 11. 比較類的錯誤方法
if(obj.getClass().getName().equals("Foo")) // Wrong!
// objects class is named Foo
}else{
// object's class has some other name
}
建議
在那些非得根據(jù)名稱來比較類的情況下,您必須格外小心,必須確保使用了當(dāng)前類的 ClassLoader 的當(dāng)前名稱空間,如清單 12 中所示:
清單 12. 比較類的更好方法
if(obj.getClass() == this.getClassLoader().loadClass("com.bar.Foo")){
// object's class is equal to
//the class that this class calls "com.bar.Foo"
}else{
// object's class is not equal to the class that
// this class calls "com.bar.Foo"
}
然而,比較類的更好方法是直接比較類對象看它們是否相等。例如,如果您想確定兩個對象 a 和 b 是否屬同一個類,那么您就應(yīng)該使用清單 13 中的代碼:
清單 13. 直接比較對象來看它們是否相等
if(a.getClass() == b.getClass()){
// objects have the same class
}else{
// objects have different classes
}
盡可能少用直接名稱比較。
不要使用內(nèi)部類
Java 字節(jié)碼沒有內(nèi)部類的概念,因為編譯器將內(nèi)部類轉(zhuǎn)換成了普通類,而如果沒有將內(nèi)部類聲明為 private,則同一個包內(nèi)的任何代碼恰好能訪問該普通類。
影響
因為有這一特性,所以包內(nèi)的惡意代碼可以訪問這些內(nèi)部類。如果內(nèi)部類能夠訪問括起外部類的字段,那么情況會變得更糟??赡芤呀?jīng)將這些字段聲明為 private,這樣內(nèi)部類就被轉(zhuǎn)換成了獨立類,但當(dāng)內(nèi)部類訪問外部類的字段時,編譯器就將這些字段從專用(private)的變?yōu)樵诎╬ackage)的作用域內(nèi)有效的。內(nèi)部類暴露了已經(jīng)夠糟糕的了,但更糟糕的是編譯器使您將某些字段成為 private 的舉動成為徒勞。
建議 如果能夠不使用內(nèi)部類就不要使用內(nèi)部類。
對付低嚴重性暴露的技巧
請遵循下列建議以避免低嚴重性靜態(tài)安全性暴露:
避免返回可變對象
檢查本機方法
避免返回可變對象
Java 方法返回對象引用的副本。如果實際對象是可改變的,那么使用這樣一個引用調(diào)用程序可能會改變它的內(nèi)容,通常這是我們所不希望見到的。
影響
請考慮這個示例:某個方法返回一個對敏感對象的內(nèi)部數(shù)組的引用,假定該方法的調(diào)用程序不改變這些對象。即使數(shù)組對象本身是不可改變的,也可以在數(shù)組對象以外操作數(shù)組的內(nèi)容,這種操作將反映在返回該數(shù)組的對象中。如果該方法返回可改變的對象,那么事情會變得更糟;外部實體可以改變在那個類中聲明的 public 變量,這種改變將反映在實際對象中。
清單 14 演示了脆弱性。getExposedObj() 方法返回了 Exposed 對象的引用副本,該對象是可變的:
清單 14. 返回可變對象的引用副本
class Exposed{
private int id;
private String name;
public Exposed(){
}
public Exposed(int id, String name){
this.id = id;
this.name = name;
}
public int getId(){
return id;
}
public String getName(){
return name;
}
public void setId(int id){
this.id=id;
}
public void setName(String name){
this.name = name;
}
public void display(){
System.out.println("Id = "+ id + " Name = "+ name);
}
}
public class Exp12{
private Exposed exposedObj = new Exposed(1,"Harry Porter");
public Exposed getExposedObj(){
return exposedObj; //returns a reference to the object.
}
public static void main(String[] args){
Exp12 exp12 = new Exp12();
exp12.getExposedObj().display();
Exposed exposed = exp12.getExposedObj();
exposed.setId(10);
exposed.setName("Hacker");
exp12.getExposedObj().display();
}
}
建議
如果方法返回可改變的對象,但又不希望調(diào)用程序改變該對象,請修改該方法使之不返回實際對象而是返回它的副本或克隆。要改正清單 14 中的代碼,請讓它返回 Exposed 對象的副本,如清單 15 中所示:
清單 15. 返回可變對象的副本
public Exposed getExposedObj(){
return new Exposed(exposedObj.getId(),exposedObj.getName());
}
或者,您的代碼也可以返回 Exposed 對象的克隆。
檢查本機方法
本機方法是一種 Java 方法,其實現(xiàn)是用另一種編程語言編寫的,如 C 或 C++。有些開發(fā)人員實現(xiàn)本機方法,這是因為 Java 語言即使使用即時(just-in-time)編譯器也比許多編譯過的語言要慢。其它人需要使用本機代碼是為了在 JVM 以外實現(xiàn)特定于平臺的功能。
影響
使用本機代碼時,請小心,因為對這些代碼進行驗證是不可能的,而且本機代碼可能潛在地允許 applet 繞過通常的安全性管理器(Security Manager)和 Java 對設(shè)備訪問的控制。
建議
如果非得使用本機方法,那么請檢查這些方法以確定:
它們返回什么
它們獲取什么作為參數(shù)
它們是否繞過安全性檢查
它們是否是 public、private 等等
它們是否含有繞過包邊界從而繞過包保護的方法調(diào)用
結(jié)束語
編寫安全 Java 代碼是十分困難的,但本文描述了一些可行的實踐來幫您編寫安全 Java 代碼。這些建議并不能解決您的所有安全性問題,但它們將減少暴露數(shù)目。最佳軟件安全性實踐可以幫助確保軟件正常運行。安全至關(guān)重要和高可靠系統(tǒng)設(shè)計者總是花費大量精力來分析和跟蹤軟件行為。只有通過將安全性作為至關(guān)緊要的系統(tǒng)特性來對待 — 并且從一開始就將它構(gòu)建到應(yīng)用程序中,我們才可以避免亡羊補牢似的、修修補補的安全性方法。
參考資料
請通過單擊文章頂部或底部的討論來參加本文的論壇。
了解關(guān)于 Java 安全性 API 的更多知識。
developerWorks 安全專題上通常含有有關(guān)計算機安全性的優(yōu)秀資源。
Larry Koved、 Anthony J. Nadalin、Don Neal 和 Tim Lawson 合作編寫的 “The evolution of Java security”(developerWorks,1998 年)對 Java 語言的安全性模型早期開發(fā)進行了深入探討。
Sing Li 在他的 Java 安全性系列文章(由兩部分組成的)(developerWorks, 2001 年 2 月)中向開發(fā)人員顯示:盡管社區(qū)可能不得不重新考慮 Java 2 中的安全性設(shè)計,還是出現(xiàn)了只對開發(fā)人員有幫助,可以滿足他們的需求的一致的進展:
第一部分
第二部分
John Viega、Tom Mutdosch、 Gary McGraw 和 Ed Felten 合著的 “Statically scanning Java code for security vulnerabilities” (IEEE Software,2000 年 9 月)介紹了一種 Java 工具,可以使用該工具來檢查您的 Java 代碼中的安全性漏洞。
G. McGraw 和 E. Felten 合作編寫的 Securing Java: Getting Down to Business with Mobile Code(John Wiley 和 Sons,1998 年)深入涵蓋了 Java 安全性。(文檔是 PDF 格式的。)
定期檢查 IBM 研究 Java 安全頁面以便 IBM 在安全性領(lǐng)域的創(chuàng)新有重要發(fā)展時能夠跟蹤這一創(chuàng)新。
如果您的 Java 代碼運行在 S/390 系統(tǒng)上,那么您將需要查閱 S/390 Java 安全頁面以獲取額外的信息。
關(guān)于作者
Bijaya Nanda Sahu 是就職于印度 IBM Global Services 的軟件工程師。他從事過各種因特網(wǎng)技術(shù)和框架(J2EE、WSBCC、JADE)、 WebSphere 相關(guān)技術(shù)、UML 和 OOAD 方面的工作。目前,他從事因特網(wǎng)銀行安全性問題方面的工作,重點在 WebSphere Application Server 和 Portal Server 上??梢酝ㄟ^ bijaya.sahu@in.ibm.com 和他聯(lián)系
;????? Java程序的源代碼很容易被別人偷看 只要有一個反編譯器 任何人都可以分析別人的代碼 本文討論如何在不修改原有程序的情況下 通過加密技術(shù)保護源代碼
一 為什么要加密?
對于傳統(tǒng)的C或C++之類的語言來說 要在Web上保護源代碼是很容易的 只要不發(fā)布它就可以 遺憾的是 Java程序的源代碼很容易被別人偷看 只要有一個反編譯器 任何人都可以分析別人的代碼 Java的靈活性使得源代碼很容易被竊取 但與此同時 它也使通過加密保護代碼變得相對容易 我們唯一需要了解的就是Java的ClassLoader對象 當(dāng)然 在加密過程中 有關(guān)Java Cryptography Extension(JCE)的知識也是必不可少的
有幾種技術(shù)可以 模糊 Java類文件 使得反編譯器處理類文件的效果大打折扣 然而 修改反編譯器使之能夠處理這些經(jīng)過模糊處理的類文件并不是什么難事 所以不能簡單地依賴模糊技術(shù)來保證源代碼的安全
我們可以用流行的加密工具加密應(yīng)用 比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard) 這時 最終用戶在運行應(yīng)用之前必須先進行解密 但解密之后 最終用戶就有了一份不加密的類文件 這和事先不進行加密沒有什么差別
Java運行時裝入字節(jié)碼的機制隱含地意味著可以對字節(jié)碼進行修改 JVM每次裝入類文件時都需要一個稱為ClassLoader的對象 這個對象負責(zé)把新的類裝入正在運行的JVM JVM給ClassLoader一個包含了待裝入類(比如java lang Object)名字的字符串 然后由ClassLoader負責(zé)找到類文件 裝入原始數(shù)據(jù) 并把它轉(zhuǎn)換成一個Class對象
我們可以通過定制ClassLoader 在類文件執(zhí)行之前修改它 這種技術(shù)的應(yīng)用非常廣泛??在這里 它的用途是在類文件裝入之時進行解密 因此可以看成是一種即時解密器 由于解密后的字節(jié)碼文件永遠不會保存到文件系統(tǒng) 所以竊密者很難得到解密后的代碼
由于把原始字節(jié)碼轉(zhuǎn)換成Class對象的過程完全由系統(tǒng)負責(zé) 所以創(chuàng)建定制ClassLoader對象其實并不困難 只需先獲得原始數(shù)據(jù) 接著就可以進行包含解密在內(nèi)的任何轉(zhuǎn)換
Java 在一定程度上簡化了定制ClassLoader的構(gòu)建 在Java 中 loadClass的缺省實現(xiàn)仍舊負責(zé)處理所有必需的步驟 但為了顧及各種定制的類裝入過程 它還調(diào)用一個新的findClass方法
這為我們編寫定制的ClassLoader提供了一條捷徑 減少了麻煩 只需覆蓋findClass 而不是覆蓋loadClass 這種方法避免了重復(fù)所有裝入器必需執(zhí)行的公共步驟 因為這一切由loadClass負責(zé)
不過 本文的定制ClassLoader并不使用這種方法 原因很簡單 如果由默認的ClassLoader先尋找經(jīng)過加密的類文件 它可以找到;但由于類文件已經(jīng)加密 所以它不會認可這個類文件 裝入過程將失敗 因此 我們必須自己實現(xiàn)loadClass 稍微增加了一些工作量
二 定制類裝入器
每一個運行著的JVM已經(jīng)擁有一個ClassLoader 這個默認的ClassLoader根據(jù)CLASSPATH環(huán)境變量的值 在本地文件系統(tǒng)中尋找合適的字節(jié)碼文件
應(yīng)用定制ClassLoader要求對這個過程有較為深入的認識 我們首先必須創(chuàng)建一個定制ClassLoader類的實例 然后顯式地要求它裝入另外一個類 這就強制JVM把該類以及所有它所需要的類關(guān)聯(lián)到定制的ClassLoader Listing 顯示了如何用定制ClassLoader裝入類文件
【Listing 利用定制的ClassLoader裝入類文件】
以下是引用片段
// 首先創(chuàng)建一個ClassLoader對象 ClassLoader myClassLoader = new myClassLoader(); // 利用定制ClassLoader對象裝入類文件 // 并把它轉(zhuǎn)換成Class對象 Class myClass = myClassLoader loadClass( mypackage MyClass ); // 最后 創(chuàng)建該類的一個實例 Object newInstance = myClass newInstance(); // 注意 MyClass所需要的所有其他類 都將通過 // 定制的ClassLoader自動裝入
如前所述 定制ClassLoader只需先獲取類文件的數(shù)據(jù) 然后把字節(jié)碼傳遞給運行時系統(tǒng) 由后者完成余下的任務(wù)
ClassLoader有幾個重要的方法 創(chuàng)建定制的ClassLoader時 我們只需覆蓋其中的一個 即loadClass 提供獲取原始類文件數(shù)據(jù)的代碼 這個方法有兩個參數(shù) 類的名字 以及一個表示JVM是否要求解析類名字的標(biāo)記(即是否同時裝入有依賴關(guān)系的類) 如果這個標(biāo)記是true 我們只需在返回JVM之前調(diào)用resolveClass
【Listing ClassLoader loadClass()的一個簡單實現(xiàn)】
以下是引用片段
public Class loadClass( String name boolean resolve ) throws ClassNotFoundException { try { // 我們要創(chuàng)建的Class對象 Class clasz = null; // 必需的步驟 如果類已經(jīng)在系統(tǒng)緩沖之中 // 我們不必再次裝入它 clasz = findLoadedClass( name ); if (clasz != null) return clasz; // 下面是定制部分 byte classData[] = /* 通過某種方法獲取字節(jié)碼數(shù)據(jù) */; if (classData != null) { // 成功讀取字節(jié)碼數(shù)據(jù) 現(xiàn)在把它轉(zhuǎn)換成一個Class對象 clasz = defineClass( name classData classData length ); } // 必需的步驟 如果上面沒有成功 // 我們嘗試用默認的ClassLoader裝入它 if (clasz == null) clasz = findSystemClass( name ); // 必需的步驟 如有必要 則裝入相關(guān)的類 if (resolve clasz != null) resolveClass( clasz ); // 把類返回給調(diào)用者 return clasz; } catch( IOException ie ) { throw new ClassNotFoundException( ie toString() ); } catch( GeneralSecurityException gse ) { throw new ClassNotFoundException( gse toString() ); } }
Listing 顯示了一個簡單的loadClass實現(xiàn) 代碼中的大部分對所有ClassLoader對象來說都一樣 但有一小部分(已通過注釋標(biāo)記)是特有的 在處理過程中 ClassLoader對象要用到其他幾個輔助方法
findLoadedClass 用來進行檢查 以便確認被請求的類當(dāng)前還不存在 loadClass方法應(yīng)該首先調(diào)用它
defineClass 獲得原始類文件字節(jié)碼數(shù)據(jù)之后 調(diào)用defineClass把它轉(zhuǎn)換成一個Class對象 任何loadClass實現(xiàn)都必須調(diào)用這個方法
findSystemClass 提供默認ClassLoader的支持 如果用來尋找類的定制方法不能找到指定的類(或者有意地不用定制方法) 則可以調(diào)用該方法嘗試默認的裝入方式 這是很有用的 特別是從普通的JAR文件裝入標(biāo)準(zhǔn)Java類時
resolveClass 當(dāng)JVM想要裝入的不僅包括指定的類 而且還包括該類引用的所有其他類時 它會把loadClass的resolve參數(shù)設(shè)置成true 這時 我們必須在返回剛剛裝入的Class對象給調(diào)用者之前調(diào)用resolveClass
三 加密 解密
Java加密擴展即Java Cryptography Extension 簡稱JCE 它是Sun的加密服務(wù)軟件 包含了加密和密匙生成功能 JCE是JCA(Java Cryptography Architecture)的一種擴展
JCE沒有規(guī)定具體的加密算法 但提供了一個框架 加密算法的具體實現(xiàn)可以作為服務(wù)提供者加入 除了JCE框架之外 JCE軟件包還包含了SunJCE服務(wù)提供者 其中包括許多有用的加密算法 比如DES(Data Encryption Standard)和Blowfish
為簡單計 在本文中我們將用DES算法加密和解密字節(jié)碼 下面是用JCE加密和解密數(shù)據(jù)必須遵循的基本步驟
步驟 生成一個安全密匙 在加密或解密任何數(shù)據(jù)之前需要有一個密匙 密匙是隨同被加密的應(yīng)用一起發(fā)布的一小段數(shù)據(jù) Listing 顯示了如何生成一個密匙 【Listing 生成一個密匙】
以下是引用片段
// DES算法要求有一個可信任的隨機數(shù)源 SecureRandom sr = new SecureRandom(); // 為我們選擇的DES算法生成一個KeyGenerator對象 KeyGenerator kg = KeyGenerator getInstance( DES ); kg init( sr ); // 生成密匙 SecretKey key = kg generateKey(); // 獲取密匙數(shù)據(jù) byte rawKeyData[] = key getEncoded(); /* 接下來就可以用密匙進行加密或解密 或者把它保存 為文件供以后使用 */ doSomething( rawKeyData );??步驟 加密數(shù)據(jù) 得到密匙之后 接下來就可以用它加密數(shù)據(jù) 除了解密的ClassLoader之外 一般還要有一個加密待發(fā)布應(yīng)用的獨立程序(見Listing ) 【Listing 用密匙加密原始數(shù)據(jù)】
以下是引用片段
// DES算法要求有一個可信任的隨機數(shù)源 SecureRandom sr = new SecureRandom(); byte rawKeyData[] = /* 用某種方法獲得密匙數(shù)據(jù) */; // 從原始密匙數(shù)據(jù)創(chuàng)建DESKeySpec對象 DESKeySpec dks = new DESKeySpec( rawKeyData ); // 創(chuàng)建一個密匙工廠 然后用它把DESKeySpec轉(zhuǎn)換成 // 一個SecretKey對象 SecretKeyFactory keyFactory = SecretKeyFactory getInstance( DES ); SecretKey key = keyFactory generateSecret( dks ); // Cipher對象實際完成加密操作 Cipher cipher = Cipher getInstance( DES ); // 用密匙初始化Cipher對象 cipher init( Cipher ENCRYPT_MODE key sr ); // 現(xiàn)在 獲取數(shù)據(jù)并加密 byte data[] = /* 用某種方法獲取數(shù)據(jù) */ // 正式執(zhí)行加密操作 byte encryptedData[] = cipher doFinal( data ); // 進一步處理加密后的數(shù)據(jù) doSomething( encryptedData );??步驟 解密數(shù)據(jù) 運行經(jīng)過加密的應(yīng)用時 ClassLoader分析并解密類文件 操作步驟如Listing 所示 【Listing 用密匙解密數(shù)據(jù)】
// DES算法要求有一個可信任的隨機數(shù)源 SecureRandom sr = new SecureRandom(); byte rawKeyData[] = /* 用某種方法獲取原始密匙數(shù)據(jù) */; // 從原始密匙數(shù)據(jù)創(chuàng)建一個DESKeySpec對象 DESKeySpec dks = new DESKeySpec( rawKeyData ); // 創(chuàng)建一個密匙工廠 然后用它把DESKeySpec對象轉(zhuǎn)換成 // 一個SecretKey對象 SecretKeyFactory keyFactory = SecretKeyFactory getInstance( DES ); SecretKey key = keyFactory generateSecret( dks ); // Cipher對象實際完成解密操作 Cipher cipher = Cipher getInstance( DES ); // 用密匙初始化Cipher對象 cipher init( Cipher DECRYPT_MODE key sr ); // 現(xiàn)在 獲取數(shù)據(jù)并解密 byte encryptedData[] = /* 獲得經(jīng)過加密的數(shù)據(jù) */ // 正式執(zhí)行解密操作 byte decryptedData[] = cipher doFinal( encryptedData ); // 進一步處理解密后的數(shù)據(jù) doSomething( decryptedData );
四 應(yīng)用實例
前面介紹了如何加密和解密數(shù)據(jù) 要部署一個經(jīng)過加密的應(yīng)用 步驟如下
步驟 創(chuàng)建應(yīng)用 我們的例子包含一個App主類 兩個輔助類(分別稱為Foo和Bar) 這個應(yīng)用沒有什么實際功用 但只要我們能夠加密這個應(yīng)用 加密其他應(yīng)用也就不在話下
步驟 生成一個安全密匙 在命令行 利用GenerateKey工具(參見GenerateKey java)把密匙寫入一個文件 % java GenerateKey key data
步驟 加密應(yīng)用 在命令行 利用EncryptClasses工具(參見EncryptClasses java)加密應(yīng)用的類 % java EncryptClasses key data App class Foo class Bar class
該命令把每一個 class文件替換成它們各自的加密版本
步驟 運行經(jīng)過加密的應(yīng)用 用戶通過一個DecryptStart程序運行經(jīng)過加密的應(yīng)用 DecryptStart程序如Listing 所示 【Listing DecryptStart java 啟動被加密應(yīng)用的程序】
以下是引用片段
import java io *; import java security *; import java lang reflect *; import javax crypto *; import javax crypto spec *; public class DecryptStart extends ClassLoader { // 這些對象在構(gòu)造函數(shù)中設(shè)置 // 以后loadClass()方法將利用它們解密類 private SecretKey key; private Cipher cipher; // 構(gòu)造函數(shù) 設(shè)置解密所需要的對象 public DecryptStart( SecretKey key ) throws GeneralSecurityException IOException { this key = key; String algorithm = DES ; SecureRandom sr = new SecureRandom(); System err println( [DecryptStart: creating cipher] ); cipher = Cipher getInstance( algorithm ); cipher init( Cipher DECRYPT_MODE key sr ); } // main過程 我們要在這里讀入密匙 創(chuàng)建DecryptStart的 // 實例 它就是我們的定制ClassLoader // 設(shè)置好ClassLoader以后 我們用它裝入應(yīng)用實例 // 最后 我們通過Java Reflection API調(diào)用應(yīng)用實例的main方法 static public void main( String args[] ) throws Exception { String keyFilename = args[ ]; String appName = args[ ]; // 這些是傳遞給應(yīng)用本身的參數(shù) String realArgs[] = new String[args length ]; System arraycopy( args realArgs args length ); // 讀取密匙 System err println( [DecryptStart: reading key] ); byte rawKey[] = Util readFile( keyFilename ); DESKeySpec dks = new DESKeySpec( rawKey ); SecretKeyFactory keyFactory = SecretKeyFactory getInstance( DES ); SecretKey key = keyFactory generateSecret( dks ); // 創(chuàng)建解密的ClassLoader DecryptStart dr = new DecryptStart( key ); // 創(chuàng)建應(yīng)用主類的一個實例 // 通過ClassLoader裝入它 System err println( [DecryptStart: loading +appName+ ] ); Class clasz = dr loadClass( appName ); // 最后 通過Reflection API調(diào)用應(yīng)用實例 // 的main()方法 // 獲取一個對main()的引用 String proto[] = new String[ ]; Class mainArgs[] = { (new String[ ]) getClass() }; Method main = clasz getMethod( main mainArgs ); // 創(chuàng)建一個包含main()方法參數(shù)的數(shù)組 Object argsArray[] = { realArgs }; System err println( [DecryptStart: running +appName+ main()] ); // 調(diào)用main() main invoke( null argsArray ); } public Class loadClass( String name boolean resolve ) throws ClassNotFoundException { try { // 我們要創(chuàng)建的Class對象 Class clasz = null; // 必需的步驟 如果類已經(jīng)在系統(tǒng)緩沖之中 // 我們不必再次裝入它 clasz = findLoadedClass( name ); if (clasz != null) return clasz; // 下面是定制部分 try { // 讀取經(jīng)過加密的類文件 byte classData[] = Util readFile( name+ class ); if (classData != null) { // 解密 byte decryptedClassData[] = cipher doFinal( classData ); // 再把它轉(zhuǎn)換成一個類 clasz = defineClass( name decryptedClassData decryptedClassData length ); System err println( [DecryptStart: decrypting class +name+ ] ); } } catch( FileNotFoundException fnfe ) // 必需的步驟 如果上面沒有成功 // 我們嘗試用默認的ClassLoader裝入它 if (clasz == null) clasz = findSystemClass( name ); // 必需的步驟 如有必要 則裝入相關(guān)的類 if (resolve clasz != null) resolveClass( clasz ); // 把類返回給調(diào)用者 return clasz; } catch( IOException ie ) { throw new ClassNotFoundException( ie toString() ); } catch( GeneralSecurityException gse ) { throw new ClassNotFoundException( gse toString() ); } } }??對于未經(jīng)加密的應(yīng)用 正常執(zhí)行方式如下 % java App arg arg arg
對于經(jīng)過加密的應(yīng)用 則相應(yīng)的運行方式為 % java DecryptStart key data App arg arg arg
DecryptStart有兩個目的 一個DecryptStart的實例就是一個實施即時解密操作的定制ClassLoader;同時 DecryptStart還包含一個main過程 它創(chuàng)建解密器實例并用它裝入和運行應(yīng)用 示例應(yīng)用App的代碼包含在App java Foo java和Bar java內(nèi) Util java是一個文件I/O工具 本文示例多處用到了它 完整的代碼請從本文最后下載
五 注意事項
我們看到 要在不修改源代碼的情況下加密一個Java應(yīng)用是很容易的 不過 世上沒有完全安全的系統(tǒng) 本文的加密方式提供了一定程度的源代碼保護 但對某些攻擊來說它是脆弱的
雖然應(yīng)用本身經(jīng)過了加密 但啟動程序DecryptStart沒有加密 攻擊者可以反編譯啟動程序并修改它 把解密后的類文件保存到磁盤 降低這種風(fēng)險的辦法之一是對啟動程序進行高質(zhì)量的模糊處理 或者 啟動程序也可以采用直接編譯成機器語言的代碼 使得啟動程序具有傳統(tǒng)執(zhí)行文件格式的安全性
另外還要記住的是 大多數(shù)JVM本身并不安全 狡猾的黑客可能會修改JVM 從ClassLoader之外獲取解密后的代碼并保存到磁盤 從而繞過本文的加密技術(shù) Java沒有為此提供真正有效的補救措施
lishixinzhi/Article/program/Java/hx/201311/25751