靜態(tài)測試包括代碼檢查、靜態(tài)結(jié)構(gòu)分析、代碼質(zhì)量度量等。它可以由人工進行,充分發(fā)揮人的邏輯思維優(yōu)勢,也可以借助軟件工具自動進行。代碼檢查代碼檢查包括代碼走查、桌面檢查、代碼審查等,主要檢查代碼和設(shè)計的一致性, 代碼對標準的遵循、可讀性,代碼的邏輯表達的正確性,代碼結(jié)構(gòu)的合理性等方面;可以發(fā)現(xiàn)違背程序編寫標準的問題,程序中不安全、不明確和模糊的部分,找出程序中不可移植部分、違背程序編程風格的問題,包括變量檢查、命名和類型審查、程序邏輯審查、程序語法檢查和程序結(jié)構(gòu)檢查等內(nèi)容?!?。看了一系列的靜態(tài)代碼掃描或者叫靜態(tài)代碼分析工具后,總結(jié)對工具的看法:靜態(tài)代碼掃描工具,和編譯器的某些功能其實是很相似的,他們也需要詞法分析,語法分析,語意分析...但和編譯器不一樣的是他們可以自定義各種各樣的復雜的規(guī)則去對代碼進行分析。
成都創(chuàng)新互聯(lián)服務(wù)項目包括青山網(wǎng)站建設(shè)、青山網(wǎng)站制作、青山網(wǎng)頁制作以及青山網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,青山網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到青山省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
1. 打好基礎(chǔ)
寫出高質(zhì)量代碼,并不是搭建空中樓閣,需要有一定的基礎(chǔ),這里我重點強調(diào)與代碼質(zhì)量密切相關(guān)的幾點:
掌握好開發(fā)語言,比如做Android就必須對Java足夠熟悉,《Effective Java》一書就是教授大家如何更好得掌握Java, 寫出高質(zhì)量Java代碼。
熟悉開發(fā)平臺, 不同的開發(fā)平臺,有不同的API, 有不同的工作原理,同樣是Java代碼,在PC上寫與Android上寫很多地方不一樣,要去熟悉Android編程的一些特性,iOS編程的一些特性,了解清楚這些,才能寫出更加地道的代碼,充分發(fā)揮各自平臺的優(yōu)勢。
基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)與算法,掌握好這些在解決一些特定問題時,可以以更加優(yōu)雅有效的方式處理。
基礎(chǔ)的設(shè)計原則,無需完全掌握23種經(jīng)典設(shè)計模式,只需要了解一些常用的設(shè)計原則即可,甚至你也可以只了解什么是低耦合,并在你的代碼中堅持實踐,也能寫出很不錯的代碼。
2. 代碼標準
代碼標準在團隊合作中尤為重要,誰也不希望一個項目中代碼風格各異,看得讓人糟心,即便是個人開發(fā)者,現(xiàn)在也需要跟各種開源項目打交道。標準怎么定是一個老生常談的話題,我個人職業(yè)生涯中經(jīng)歷過很多次的代碼標準討論會議,C++, C#, Java等等,大家有時會堅持自己的習慣不肯退讓??涩F(xiàn)如今時代不一樣了,Google等大廠已經(jīng)為我們制定好了各種標準,不用爭了,就用這些業(yè)界標準吧。
3. 想好再寫
除非你很清楚你要怎么做,否則我不建議邊做邊想。
你真的搞清楚你要解決的問題是什么了嗎?你的方案是否能有效?有沒有更優(yōu)雅簡單的方案?準備怎么設(shè)計它,必要的情況下,需要有設(shè)計文檔,復雜一些的設(shè)計需要有同行評審,寫代碼其實是很簡單的事情,前提是你得先想清楚。
4. 代碼重構(gòu)
重構(gòu)對于代碼質(zhì)量的重要性不言而喻,反正我是很難一次把代碼寫得讓自己滿意、無可挑剔,《重構(gòu)》這本書作為業(yè)內(nèi)經(jīng)典也理應(yīng)人人必讀,也有其他類似的教授重構(gòu)技巧的書,有些也非常不錯,遺憾的是我發(fā)現(xiàn)很多工作多年的同學甚至都沒有了解過重構(gòu)的概念。
5. 技術(shù)債務(wù)
知乎上最近有個熱門問題《為什么有些大公司技術(shù)弱爆了?》,其實里面提到的很多歸根結(jié)底都是技術(shù)債務(wù)問題,這在一些大公司尤為常見。技術(shù)債務(wù)話題太大,但就代碼質(zhì)量而言,我只想提一下不要因為這些債是前人留下的你就不去管,現(xiàn)實是沒有多少機會讓你從一個清爽清新的項目開始做起,你不得不去面對這些,你也沒法完全不跟這些所謂的爛代碼打交道。
因此我建議各位:當你負責一個小模塊時,除了把它做好之外,也要順便將與之糾纏在一起的技術(shù)債務(wù)還掉,因為這些債務(wù)最終將是整個團隊來共同承擔,任何一個人都別想獨善其身,如果你還對高質(zhì)量代碼有追求的話。
作為團隊的技術(shù)負責人,也要頂住壓力,鼓勵大家勇于做出嘗試,引導大家不斷改進代碼質(zhì)量,不要總是畏手畏腳,停滯不前,真要背鍋也得上,要有擔當。
6. 代碼審查
我曾經(jīng)聽過一些較高級別的技術(shù)分享,竟然還不時聽到一些呼吁大家要做代碼審查的主題,我以為在這個級別的技術(shù)會議上,不應(yīng)再討論代碼審查有什么好,為什么要做代碼審查之類的問題。同時我接觸過相當多所謂國內(nèi)一線互聯(lián)網(wǎng)公司,竟有許多是不做代碼審查的,這一度讓我頗為意外。
這里也不想多談如何做好代碼審查,只是就代碼質(zhì)量這點,不客氣地說:沒有過代碼審查經(jīng)歷的同學,往往很難寫出高質(zhì)量的代碼,尤其是在各種追求速度的糙快猛創(chuàng)業(yè)公司。
7. 靜態(tài)檢查
很多代碼上的問題,都可以通過一些工具來找到,某些場景下,它比人要靠譜得多,至少不會出現(xiàn)某些細節(jié)上的遺漏,同時也能有效幫助大家減少代碼審查的工作量。
Android開發(fā)中有Lint, Find bugs, PMD等優(yōu)秀靜態(tài)檢查工具可用,通過改進這些工具找出的問題,就能對語法的細節(jié),規(guī)范,編程的技巧有更多直觀了解。
建議最好與持續(xù)集成(CI),代碼審查環(huán)境配套使用, 每次提交的代碼都能自動驗證是否通過了工具的代碼檢查,通過才允許提交。
8. 單元測試
Android單元測試,一直備受爭議,主要還是原生的測試框架不夠方便,每跑一次用例需要在模擬器或者真機上運行,效率太低,也不方便在CI環(huán)境下自動構(gòu)建單元測試,好在有Robolectric,能幫我們解決部分問題。
單元測試的一個非常顯著的優(yōu)點是,當你需要修改大量代碼時,盡管放心修改,只需要保證單元測試用例通過即可,無需瞻前顧后。
9. 充分自測
有一種說法:程序員最害怕的是他自己寫的代碼,尤其是準備在眾人面前show自己的工作成果時,因此在寫完代碼后,需要至少跑一遍基本的場景,一些簡單的異常流。在把你的工作成果提交給測試或用戶前,充分自測是基本的職業(yè)素養(yǎng),不要總想著讓測試幫你找問題,隨便用幾下就Crash的東西,你好意思拿給別人嗎?
10. 善用開源
并非開源的東西,質(zhì)量就高,但至少關(guān)注度較高,使用人數(shù)較多,口碑較好的開源項目,質(zhì)量是有一定保證的,這其中的道理很簡單。即便存在一些問題,也可以通過提交反饋,不斷改進。最重要的是,你自己花時間造的輪子,需要很多精力維護,而充分利用開源項目,能幫助你節(jié)省很多時間,把精力專注在最需要你關(guān)心的問題上。
在本文中,我們討論了對付 13 種不同靜態(tài)暴露的技巧。對于每種暴露,我們解釋了不處理這些安全性問題所造成的影響。我們還為您推薦了一些準則,要開發(fā)不受這些靜態(tài)安全性暴露威脅的、健壯且安全的 Java 應(yīng)用程序,您應(yīng)該遵循這些準則。一有合適的時機,我們就提供代碼樣本(既有暴露的代碼也有無暴露的代碼)。
對付高嚴重性暴露的技巧
請遵循下列建議以避免高嚴重性靜態(tài)安全性暴露:
限制對變量的訪問
讓每個類和方法都成為 final,除非有足夠的理由不這樣做
不要依賴包作用域
使類不可克隆
使類不可序列化
使類不可逆序列化
避免硬編碼敏感數(shù)據(jù)
查找惡意代碼
限制對變量的訪問
如果將變量聲明為 public,那么外部代碼就可以操作該變量。這可能會導致安全性暴露。
影響
如果實例變量為 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)點之一。在試圖提供安全性時,可擴展性卻成了您的敵人;可擴展性只會為攻擊者提供更多給您帶來麻煩的方法。
不要依賴包作用域
沒有顯式地標注為 public、private 或 protected 的類、方法和變量在它們自己的包內(nèi)是可訪問的。
影響
如果 Java 包不是封閉的,那么攻擊者就可以向包內(nèi)引入新類并使用該新類來訪問您想保護的內(nèi)容。諸如 java.lang 之類的一些包缺省是封閉的,一些 JVM 也讓您封閉自己的包。然而,您最好假定包是不封閉的。
建議
從軟件工程觀點來看,包作用域具有重要意義,因為它可以阻止對您想隱藏的內(nèi)容進行偶然的、無意中的訪問。但不要依靠它來獲取安全性。應(yīng)該將類、方法和變量顯式標注為 public、private 或 protected 中適合您特定需求的那種。
使類不可克隆
克隆允許繞過構(gòu)造器而輕易地復制類實例。
影響
即使您沒有有意使類可克隆,外部源仍然可以定義您的類的子類,并使該子類實現(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ù)保存在外部文件中。闖入代碼可以克隆或復制實例,然后對它進行序列化。
影響
序列化是令人擔憂的,因為它允許外部源獲取對您的對象的內(nèi)部狀態(tài)的控制。這一外部源可以將您的對象之一序列化成攻擊者隨后可以讀取的字節(jié)數(shù)組,這使得攻擊者可以完全審查您的對象的內(nèi)部狀態(tài),包括您標記為 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é)序列。這種可能為您帶來了大量風險,因為您不能控制逆序列化對象的狀態(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() 方法,這有助于測試單個類的功能。當類從測試轉(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īng)該有一個在構(gòu)造器中設(shè)置的私有布爾標志,如清單 9 中的類所示。在每個非 static 方法中,代碼在任何進一步執(zhí)行之前都應(yīng)該檢查該標志的值。如果該標志的值為 true,那么控制應(yīng)該進一步繼續(xù);否則,控制應(yīng)該拋出一個例外并停止執(zhí)行。那些從構(gòu)造器調(diào)用的方法將不會檢查初始化的變量,因為在調(diào)用方法時沒有設(shè)置標志。因為這些方法并不檢查標志,所以應(yīng)該將它們聲明為 private 以防止用戶直接訪問它們。
清單 9. 使用布爾標志以檢查初始化過程
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() 的對象都可能充當對象驗證器,但對象通常驗證它自己對其它對象的引用。RegisterValidation() 的第二個參數(shù)是一個確定回調(diào)順序的整數(shù)優(yōu)先級,優(yōu)先級數(shù)字大的比優(yōu)先級數(shù)字小的先回調(diào)。同一優(yōu)先級內(nèi)的回調(diào)順序則不確定。
當對象已逆序列化時,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ù)名稱來比較類的情況下,您必須格外小心,必須確保使用了當前類的 ClassLoader 的當前名稱空間,如清單 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)換成了獨立類,但當內(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點抗 和他聯(lián)系
保證代碼質(zhì)量的順序如下:
1)代碼風格
在項目開發(fā)之初,往往會制定一個代碼編寫的規(guī)范,實際上,這個代碼規(guī)范就包含了整個項目組的代碼風格。由于軟件開發(fā)人員的設(shè)計習慣不同,如果不統(tǒng)一代碼風格,一個項目中的代碼將五花八門,如變量和常量的命名、接口與實現(xiàn)類的注釋、何時回車、怎樣縮進等等。一個五花八門的設(shè)計風格,必將為日后的維護與改進帶來困難。
通過代碼復查,一方面督促開發(fā)人員按照規(guī)范編寫代碼,另一方面也使開發(fā)人員自身形成良好的編程習慣。代碼風格的審查,由于內(nèi)容比較單一,常常可以通過一些代碼復查的工具來自動完成,提高復查的效率。
2)重大缺陷
在一些關(guān)于代碼復查的文章中,列出了一個常常的單子,描述了代碼復查應(yīng)當著重注意的重大缺陷,它們包括:存在SQL注入、易受跨站點腳本攻擊、緩存區(qū)溢出、托管代碼等等。項目組可以不斷積累重大缺陷的審查項目,并在每次審查中逐一檢查。重大缺陷審查是一個繁瑣而細致的工作,如果能編寫或使用一些審查軟件,可以大大提高審查效率。
3)設(shè)計邏輯與思路的審查
審查是代碼復查中最核心、最有價值的部分。代碼風格與重大缺陷的審查,雖然重要但簡單而機械,可以通過軟件自動檢查;而設(shè)計邏輯與思路的審查,卻是復雜而有深度的審查,需要有一定理論深度和編碼經(jīng)驗的人才能完成,而且對新手尤其重要。前面提到,新手是任何項目組不可避免的問題。
通過代碼復查,讓老手去指導新手,讓團隊整體素質(zhì)得到提高。具體辦法就是,在新手完成編碼以后,讓老手去進行代碼復查,指出新手的問題,指導新手設(shè)計。這樣的過程最初可能需要重構(gòu),甚至重新編碼。但經(jīng)過這樣的過程,新手將逐漸熟練,迅速成為老手,使整體團隊素質(zhì)提高。