天天看點

Fortify常見漏洞解決方案

閱讀文本大概需要3分鐘。

Log Forging漏洞:

1.資料從一個不可信賴的資料源進入應用程式。 在這種情況下,資料經由getParameter()到背景。 

2. 資料寫入到應用程式或系統日志檔案中。 這種情況下,資料通過info() 記錄下來。為了便于以後的審閱、統計資料收集或調試,應用程式通常使用日志檔案來儲存事件或事務的曆史記錄。根據應用程式自身的特性,審閱日志檔案可在必要時手動執行,也可以自動執行,即利用工具自動挑選日志中的重要事件或帶有某種傾向性的資訊。如果攻擊者可以向随後會被逐字記錄到日志檔案的應用程式提供資料,則可能會妨礙或誤導日志檔案的解讀。最理想的情況是,攻擊者可能通過向應用程式提供包括适當字元的輸入,在日志檔案中插入錯誤的條目。如果日志檔案是自動處理的,那麼攻擊者可以破壞檔案格式或注入意外的字元,進而使檔案無法使用。更陰險的攻擊可能會導緻日志檔案中的統計資訊發生偏差。通過僞造或其他方式,受到破壞的日志檔案可用于掩護攻擊者的跟蹤軌迹,甚至還可以牽連第三方來執行惡意行為。最糟糕的情況是,攻擊者可能向日志檔案注入代碼或者其他指令,利用日志處理實用程式中的漏洞。

例 1: 下列 Web 應用程式代碼會嘗試從一個請求對象中讀取整數值。如果數值未被解析為整數,輸入就會被記錄到日志中,附帶一條提示相關情況的錯誤消息。 

String val = request.getParameter("val");
try {
    int value = Integer.parseInt(val);
} catch (NumberFormatException nfe) {
    log.info("Failed to parse val = " + val);
}
           

如果使用者為“val”送出字元串“twenty-one”,則日志中會記錄以下條目:

INFO: Failed to parse val=twenty-one
           

然而,如果攻擊者送出字元串

“twenty-one%0a%0aINFO:+User+logged+out%3dbadguy”,
           

則日志中會記錄以下條目:

INFO: Failed to parse val=twenty-one

INFO: User logged out=badguy
           

顯然,攻擊者可以使用同樣的機制插入任意日志條目。

有些人認為在移動世界中,典型的 Web 應用程式漏洞(如 Log Forging)是無意義的 -- 為什麼使用者要攻擊自己?但是,謹記移動平台的本質是從各種來源下載下傳并在相同裝置上運作的應用程式。惡意軟體在銀行應用程式附近運作的可能性很高,它們會強制擴充移動應用程式的攻擊面(包括跨程序通信)。

例 2:以下代碼将例 1 改編為适用于 Android 平台。

String val = this.getIntent().getExtras().getString("val");
try {
int value = Integer.parseInt();
}
catch (NumberFormatException nfe) {
Log.e(TAG, "Failed to parse val = " + val);
}
           

使用間接方法防止 Log Forging 攻擊:建立一組與不同僚件一一對應的合法日志條目,這些條目必須記錄在日志中,并且僅記錄該組條目。要捕獲動态内容(如使用者登出系統),請務必使用由伺服器控制的數值,而非由使用者提供的資料。這就確定了日志條目中絕不會直接使用由使用者提供的輸入。 

可以按以下方式将例 1 重寫為與NumberFormatException 對應的預定義日志條目:

public static final String NFE = "Failed to parse val. The input is required to be an integer value."
String val = request.getParameter("val"); 
try { 
     int value = Integer.parseInt(val); 
} catch (NumberFormatException nfe) {
 log.info(NFE); 
}
           
下面是 Android 的等同内容:
           
public static final String NFE = "Failed to parse val. The input is required to be an integer value.";
String val = this.getIntent().getExtras().getString("val"); try { 
    int value = Integer.parseInt(); 
} catch (NumberFormatException nfe) { 
    Log.e(TAG, NFE); 
}
           
在某些情況下,這個方法有些不切實際,因為這樣一組合法的日志條目實在太大或是太複雜了。這種情況下,開發者往往又會退而采用黑名單方法。在輸入之前,黑名單會有選擇地拒絕或避免潛在的危險字元。然而,不安全字元清單很快就會不完善或過時。更好的方法是建立一份白名單,允許其中的字元出現在日志條目中,并且隻接受完全由這些經認可的字元組成的輸入。在大多數 Log Forging 攻擊中,最關鍵的字元是“\n”(換行符),該字元決不能出現在日志條目白名單中。
           

Tips:

1. 許多日志功能隻是為了在開發和測試過程中調試程式而建立的。根據我們的經驗,當生産的某一階段,會随機或出于某一目的進行調試。不要僅僅因為程式員說“我沒有計劃在生産中啟動調試功能”,就容忍 Log Forging 漏洞。

2. 許多現代 Web 架構都提供對使用者輸入執行驗證的機制。其中包括 Struts 和 Spring MVC。為了突出顯示未經驗證的輸入源,HPE Security Fortify 安全編碼規則包會降低 HPE Security Fortify Static Code Analyzer(HPE Security Fortify 靜态代碼分析器)報告的問題被利用的可能性,并在使用架構驗證機制時提供相應的依據,以動态重新調整問題優先級。我們将這種功能稱之為上下文敏感排序。為了進一步幫助 HPE Security Fortify 使用者執行審計過程,HPE Security Fortify 軟體安全研究團隊提供了資料驗證項目模闆,該模闆會根據應用于輸入源的驗證機制,将問題分組到多個檔案夾中。

Null Dereference

1、當違反程式員的一個或多個假設時,通常會出現 null 指針異常。如果程式明确将對象設定為 null,但稍後卻間接引用該對象,則将出現 dereference-after-store 錯誤。此錯誤通常是因為程式員在聲明變量時将變量初始化為 null。在這種情況下,在第 443 行間接引用該變量時,變量有可能為 null,進而引起 null 指針異常。 大部分空指針問題隻會引起一般的軟體可靠性問題,但如果攻擊者能夠故意觸發空指針間接引用,攻擊者就有可能利用引發的異常繞過安全邏輯,或緻使應用程式洩漏調試資訊,這些資訊對于規劃随後的攻擊十分有用。

示例:在下列代碼中,程式員将變量foo 明确設定為 null。稍後,程式員間接引用 foo,而未檢查對象是否為 null 值。

Foo foo = null;...
foo.setBar(val);
}
           
在間接引用可能為 null 值的對象之前,請務必仔細檢查。如有可能,在處理資源的代碼周圍的包裝器中納入 null 檢查,確定在所有情況下均會執行 null 檢查,并最大限度地減少出錯的地方。
           

Unreleased Resource: Streams

程式可能無法成功釋放某一項系統資源。這種情況下,盡管程式沒有釋放RuleUtils.java 檔案第 91 行所配置設定的資源,但執行這一操作程式路徑依然存在。資源洩露至少有兩種常見的原因:

-錯誤狀況及其他異常情況。

-未明确程式的哪一部份負責釋放資源。

大部分 Unreleased Resource 問題隻會導緻一般的軟體可靠性問題,但如果攻擊者能夠故意觸發資源洩漏,該攻擊者就有可能通過耗盡資源池的方式發起 denial of service 攻擊。

示例:下面的方法絕不會關閉它所打開的檔案句柄。FileInputStream 中的 finalize() 方法最終會調用close(),但是不能确定何時會調用finalize() 方法。在繁忙的環境中,這會導緻 JVM 用盡它所有的檔案句柄。

private void processFile(String fName) throws FileNotFoundException, IOException {
  FileInputStream fis = new FileInputStream(fName);
  int sz;
  byte[] byteArray = new byte[BLOCK_SIZE];
  while ((sz = fis.read(byteArray)) != -1) {
    processBytes(byteArray, sz);
  }
}
           

1. 請不要依賴 finalize() 回收資源。為了使對象的 finalize() 方法能被調用,垃圾收集器必須确認對象符合垃圾回收的條件。但是垃圾收集器隻有在 JVM 記憶體過小時才會使用。是以,無法保證何時能夠調用該對象的 finalize() 方法。垃圾收集器最終運作時,可能出現這樣的情況,即在短時間内回收大量的資源,這種情況會導緻“突發”性能,并降低總體系統通過量。随着系統負載的增加,這種影響會越來越明顯。

最後,如果某一資源回收操作被挂起(例如該操作需要通過網絡通路資料庫),那麼執行 finalize() 方法的線程也将被挂起。

2. 在 finally 代碼段中釋放資源。示例中的代碼可按以下方式改寫:

public void processFile(String fName) throws FileNotFoundException, IOException {
    FileInputStream fis;
    try {
        fis = new FileInputStream(fName);
    int sz;
    byte[] byteArray = new byte[BLOCK_SIZE];
    while ((sz = fis.read(byteArray)) != -1) {
        processBytes(byteArray, sz);
        }
    } finally {
        if (fis != null) {
            safeClose(fis);
        }
    }
}

public static void safeClose(FileInputStream fis) {
    if (fis != null) {
    try {
    fis.close();
    } catch (IOException e) {
        log(e);
    }
    }
}
           

以上方案使用了一個助手函數,用以記錄在嘗試關閉流時可能發生的異常。該助手函數大約會在需要關閉流時重新使用。

同樣,processFile 方法不會将 fis 對象初始化為 null。而是進行檢查,以確定調用 safeClose() 之前,fis 不是null。如果沒有檢查 null,Java 編譯器會報告 fis 可能沒有進行初始化。編譯器做出這一判斷源于 Java 可以檢測未初始化的變量。如果用一種更加複雜的方法将 fis 初始化為null,那麼編譯器就無法檢測 fis 未經初始化便使用的情況。

Portability Flaw: File Separator

不同的作業系統使用不同的字元作為檔案分隔符。例如,Microsoft Windows 系統使用“\”,而 UNIX 系統則使用“/”。應用程式需要在不同的平台上運作時,使用寫死檔案分隔符會導緻應用程式邏輯執行錯誤,并有可能導緻 denial of service。在這種情況下,在 FileUtil.java 中第 254行的 File() 調用中使用了寫死檔案分隔符。

例 1:以下代碼使用寫死檔案分隔符來打開檔案:

File file = new File(directoryName + "\\" + fileName);
           

為編寫可移植代碼,不應使用寫死檔案分隔符,而應使用語言庫提供的獨立于平台的 API。

例 2:下列代碼執行與例 1 相同的功能,但使用獨立于平台的 API 來指定檔案分隔符:

File file = new File(directoryName + File.separator + fileName);
           

Portability Flaw: Locale Dependent Comparison

對可能與區域設定相關的資料進行比較時,應指定相應的區域設定。

示例 1:以下示例嘗試執行驗證,以确定使用者輸入是否包含 <script> 标簽。

public String tagProcessor(String tag){
if (tag.toUpperCase().equals("SCRIPT")){
return null;
}
//does not contain SCRIPT tag, keep processing input...
}
           

關于上述代碼的問題是:在使用不帶區域設定的 java.lang.String.toUpperCase()時,其将使用預設的區域設定規則。使用土耳其區域設定 "title".toUpperCase()時将傳回 "T\u0130TLE",其中 "\u0130" 是 "LATIN CAPITAL LETTER I WITH DOT ABOVE" 字元。這會導緻生成意外結果,例如,在示例 1 中,會導緻此驗證無法捕獲 "script" 一詞,進而可能造成跨站腳本攻擊漏洞。

為了防止出現此問題,請始終確定指定預設區域設定,或者指定可以接受這些字元(如 toUpperCase())并帶有 API 的區域設定。 

示例 2:以下示例通過手動方式将區域設定指定為 toUpperCase() 的參數。

import java.util.Locale;

public String tagProcessor(String tag){
if (tag.toUpperCase(Locale.ENGLISH).equals("SCRIPT")){
return null;
}
//does not contain SCRIPT tag, keep processing input
...}
           

示例 3:以下示例使用了函數java.lang.String.equalsIgnoreCase()API 以防止出現此問題。

public String tagProcessor(String tag){
if (tag.equalsIgnoreCase("SCRIPT")){
return null;
}
//does not contain SCRIPT tag, keep processing input
...
}
           

因為 equalsIgnoreCase() 會更改與Character.toLowerCase() 和Character.toUpperCase() 類似的内容,是以可以防止此問題。這涉及到使用來自 UnicodeData 檔案(由 Unicode 聯盟維護的 Unicode 字元資料庫的一部分)的資訊建立這兩種字元串的臨時标準格式。即使這可能會導緻這些字元在被讀取時以不可讀的方式呈現出來,但卻能夠在獨立于區域設定的情況下進行比較。

Tips:

1. 如果 SCA 識别到java.util.Locale.setDefault() 可在應用程式中的任意位置進行調用,其會假定已執行了相應的區域設定,并且這些問題也不會出現。

Access Specifier Manipulation

AccessibleObject API 允許程式員繞過由 Java 通路說明符提供的 access control 檢查。特别是它讓程式員能夠允許反映對象繞過 Java access control,并反過來更改私有字段或調用私有方法、行為,這些通常情況下都是不允許的。

在此情況下,您正在使用的危險方法是BaseTestCase.java 的第 45 行中的setAccessible()。

隻能使用攻擊者無法設定的參數,通過有權限的類更改通路說明符。所有出現的通路說明符都應仔細檢查。

J2EE Bad Practices: Non-Serializable Object Stored in Session

一個 J2EE 應用程式可以利用多個 JVM,以提高應用程式的可靠性和性能。為了在最終使用者中将多個 JVM 顯示為單個的應用程式,J2EE 容器可以在多個 JVM 之間複制 HttpSession 對象,是以當一個 JVM 不可用時,另一個 JVM 可以在不中斷應用程式流程的情況下接替步驟的執行。

為了使會話複制能夠正常運作,作為應用程式屬性存儲在會話中的數值必須實作 Serializable 接口。 

例 1:下面這個類把自己添加到會話中,但由于它不是可序列化的,是以該會話就再也不能被複制了。

public class DataGlob {
    String globName;
    String globValue;
    public void addToSession(HttpSession session) {
        session.setAttribute("glob", this);
    }
}
           

很多情況下,要修複這一問題,最簡單的方法是讓這個違反規則的對象實作 Serializable 接口。 

例 2: 例 1 中的代碼應該用以下方式重寫:

public class DataGlob implements java.io.Serializable {
    String globName;
    String globValue;
    public void addToSession(HttpSession session) {
        session.setAttribute("glob", this);
    }
}
           

注意,對複雜的對象來說,存儲在會話中的對象,其傳遞閉包必須是可序列化的。如果對象 A 引用對象 B,且對象 A 存儲在會話中,那麼 A 和 B 都必須實作 Serializable 接口。

雖然實作 Serializable 接口通常都很簡單(因為該接口不要求類定義任何方法),但是某些類型的對象實作會引發一些相關問題。應密切注意引用外部資源檔案的對象。例如,資料流和 JNI 都可能會引發一些相關問題。

例 3:使用類型檢測調用可序列化對象。而不是使用:

public static void addToSession(HttpServletRequest req,String attrib, Object obj){
   HttpSession sess = req.getSession(true);
    sess.setAttribute(attrib, obj);
}
           
采用如下方法編寫:
           
public static void addToSession(HttpServletRequest req,String attrib, Serializable ser) {
  HttpSession sess = req.getSession(true);
  sess.setAttribute(attrib, ser);
}
           

Insecure Randomness

在對安全性要求較高的環境中,使用一個能産生可預測數值的函數作為随機資料源,會産生 Insecure Randomness 錯誤。在這種情況下,生成弱随機數的函數是 random(),它位于DataFormatUtils.java 檔案的第 577行。電腦是一種具有确定性的機器,是以不可能産生真正的随機性。僞随機數生成器 (PRNG) 近似于随機算法,始于一個能計算後續數值的種子。PRNG 包括兩種類型:統計學的 PRNG 和密碼學的 PRNG。統計學的 PRNG 可提供有用的統計資料,但其輸出結果很容易預測,是以資料流容易複制。若安全性取決于生成數值的不可預測性,則此類型不适用。密碼學的 PRNG 通過可産生較難預測的輸出結果來應對這一問題。為了使加密數值更為安全,必須使攻擊者根本無法、或極不可能将它與真實的随機數加以區分。通常情況下,如果并未聲明 PRNG 算法帶有加密保護,那麼它有可能就是一個統計學的 PRNG,不應在對安全性要求較高的環境中使用,其中随着它的使用可能會導緻嚴重的漏洞(如易于猜測的密碼、可預測的加密密鑰、會話劫持攻擊和 DNS 欺騙)。

示例: 下面的代碼可利用統計學的 PRNG 為購買産品後仍在有效期内的收據建立一個 URL。

String GenerateReceiptURL(String baseUrl) {
  Random ranGen = new Random();
  ranGen.setSeed((new Date()).getTime());
  return (baseUrl + ranGen.nextInt(400000000) + ".html");
}
           

這段代碼使用 Random.nextInt() 函數為它所生成的收據頁面生成獨特的辨別符。因為 Random.nextInt() 是一個統計學的 PRNG,攻擊者很容易猜到由它所生成的字元串。盡管收據系統的底層設計也存在錯誤,但如果使用了一個不生成可預測收據辨別符的随機數生成器(如密碼學的 PRNG),會更安全一些。

當不可預測性至關重要時,如大多數對安全性要求較高的環境都采用随機性,這時可以使用密碼學的 PRNG。不管選擇了哪一種 PRNG,都要始終使用帶有充足熵的數值作為該算法的種子。(諸如目前時間之類的數值隻提供很小的熵,是以不應該使用。)Java 語言在java.security.SecureRandom 中提供了一個加密 PRNG。就像 java.security中其他以算法為基礎的類那樣,SecureRandom 提供了與某個特定算法集合相關的包,該包可以獨立實作。當使用SecureRandom.getInstance() 請求一個 SecureRandom 執行個體時,您可以申請實作某個特定的算法。如果算法可行,那麼您可以将它作為SecureRandom 的對象使用。如果算法不可行,或者您沒有為算法明确特定的實作方法,那麼會由系統為您選擇 SecureRandom 的實作方法。 

Sun 在名為 SHA1PRNG 的 Java 版本中提供了一種單獨實作 SecureRandom的方式,Sun 将其描述為計算:

“SHA-1 可以計算一個真實的随機種子參數的散列值,同時,該種子參數帶有一個 64 比特的電腦,會在每一次操作後加 1。在 160 比特的 SHA-1 輸出中,隻能使用 64 比特的輸出 [1]。” 

然而,文檔中有關 Sun 的 SHA1PRNG算法實作細節的相關記錄很少,人們無法了解算法實作中使用的熵的來源,是以也并不清楚輸出中到底存在多少真實的随機數值。盡管有關 Sun 的實作方法網絡上有各種各樣的猜測,但是有一點無庸置疑,即算法具有很強的加密性,可以在對安全性極為敏感的各種内容中安全地使用。

XML External Entity Injection

問題描述:XML External Entities 攻擊可利用能夠在處理時動态建構文檔的 XML 功能。XML 實體可動态包含來自給定資源的資料。外部實體允許 XML 文檔包含來自外部 URI 的資料。除非另行配置,否則外部實體會迫使 XML 解析器通路由 URI 指定的資源,例如位于本地計算機或遠端系統上的某個檔案。這一行為會将應用程式暴露給 XML External Entity (XXE) 攻擊,進而用于拒絕本地系統的服務,擷取對本地計算機上檔案未經授權的通路權限,掃描遠端計算機,并拒絕遠端系統的服務。

下面的 XML 文檔介紹了 XXE 攻擊的示例。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///dev/random" >]><foo>&xxe;</foo>
           

如果 XML 解析器嘗試使用 /dev/random 檔案中的内容來替代實體,則此示例會使伺服器(使用 UNIX 系統)崩潰。

解決方案:應對 XML 解析器進行安全配置,使它不允許将外部實體包含在傳入的 XML 文檔中。

為了避免 XXE injections,應為 XML 代理、解析器或讀取器設定下面的屬性:

factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
           
如果不需要 inline DOCTYPE 聲明,可使用以下屬性将其完全禁用:
           
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
           

Dynamic Code Evaluation: Unsafe Deserialization

問題描述:Java 序列化會将對象圖轉換為位元組流(包含對象本身和必要的中繼資料),以便通過位元組流進行重構。開發人員可以建立自定義代碼,以協助 Java 對象反序列化過程,在此期間,他們甚至可以使用其他對象或代理替代反序列化對象。在對象重構過程中,并在對象傳回至應用程式并轉換為預期的類型之前,會執行自定義反序列化過程。到開發人員嘗試強制執行預期的類型時,代碼可能已被執行。 

在必須存在于運作時類路徑中且無法由攻擊者注入的可序列化類中,會自定義反序列化例程,是以這些攻擊的可利用性取決于應用程式環境中的可用類。令人遺憾的是,常用的第三方類,甚至 JDK 類都可以被濫用,導緻 JVM 資源耗盡、部署惡意檔案或運作任意代碼。

解決方案:如果可能,在沒有驗證對象流的内容的情況下,請勿對不可信資料進行反序列化。為了驗證要進行反序列化的類,應使用前瞻反序列化模式。 

對象流首先将包含類描述中繼資料,然後包含其成員字段的序列化位元組。Java 序列化過程允許開發人員讀取類描述,并确定是繼續進行對象的反序列化還是中止對象的反序列化。為此,需要在應執行類驗證和确認的位置,子類化java.io.ObjectInputStream 并提供resolveClass(ObjectStreamClass desc)方法的自定義實作。 

已有易于使用的前瞻模式實作方式,例如 Apache Commons IO (org.apache.commons.io.serialization.ValidatingObjectInputStream)。始終使用嚴格的白名單方法,以僅允許對預期類型進行反序列化。不建議使用黑名單方法,因為攻擊者可以使用許多可用小工具繞過黑名單。此外,請謹記,盡管用于執行代碼的某些類已公開,但是還可能存在其他未知或未公開的類,是以,白名單方法始終都是首選方法。應審計白名單中允許的任何類,以確定對其進行反序列化是安全的。

為避免 Denial of Service,建議您覆寫 resolveObject(Object obj) 方法,以便計算要進行反序列化的對象數量,并在超過門檻值時中止反序列化。

在庫或架構(例如,使用 JMX、RMI、JMS、HTTP Invoker 時)中執行反序列化時,上述建議并不适用,因為它超出了開發人員的控制範圍。在這些情況下,您可能需要確定這些協定滿足以下要求:

- 未公開披露。

- 使用身份驗證。

- 使用完整性檢查。

- 使用加密。

此外,每當應用程式通過ObjectInputStream 執行反序列化時,HPE Security Fortify Runtime(HPE Security Fortify 運作時)都會提供要強制執行的安全控制,以此同時保護應用程式代碼以及庫和架構代碼,防止遭到此類攻擊。

System Information Leak: External

問題描述:當系統資料或調試資訊通過套接字或網絡連接配接使程式流向遠端機器時,就會發生外部資訊洩露。外部資訊洩露會暴露有關作業系統、完整路徑名、現有使用者名或配置檔案位置的特定資料,進而使攻擊者有機可乘,它比内部資訊(攻擊者更難通路)洩露更嚴重。

在這種情況下,AjaxData.java 的第865 行會調用 write()。

例 1: 以下代碼洩露了 HTTP 響應中的異常資訊:

protected void doPost (HttpServletRequest req, HttpServletResponse res) throws IOException {
...
PrintWriter out = res.getWriter(); try {
...
} catch (Exception e) { out.println(e.getMessage()); } }
           

該資訊可以顯示給遠端使用者。在某些情況下,該錯誤消息恰好可以告訴攻擊者入侵這一系統的可能性究竟有多大。例如,一個資料庫錯誤消息可以揭示應用程式容易受到 SQL Injection 攻擊。其他的錯誤消息可以揭示有關該系統的更多間接線索。在上述例子中,洩露的資訊可能會暗示作業系統的類型、系統上安裝了哪些應用程式,以及管理者在配置應用程式時做了哪些方面的努力。

在移動世界,資訊洩露也讓人擔憂。移動平台的本質是從各種來源下載下傳并在相同裝置上運作的應用程式。因為惡意軟體在銀行應用程式附近運作的可能性很高,是以應用程式的作者需要注意消息所包含的資訊,這些消息将會發送給在裝置上運作的其他應用程式。

例 2:以下代碼向所有注冊 Android 的接收者廣播捕獲到的堆棧跟蹤異常。

try {
} catch (Exception e) {
  String exception = Log.getStackTraceString(e);
  Intent i = new Intent();
  i.setAction("SEND_EXCEPTION");
  i.putExtra("exception", exception);
  view.getContext().sendBroadcast(i);
}
           

這是另一種情況,特定于移動世界。大多數移動裝置現在執行的是“近場通信”(NFC) 協定,以便使用無線電通信在裝置之間快速共享資訊。它在裝置極為貼近或互相接觸時有效。即使 NFC 的通信範圍僅局限于幾厘米,也可能發生竊聽、修改資料以及各種其他類型的攻擊情況,因為 NFC 本身并不能確定通信安全。

例 3:Android 平台為 NFC 提供了支援。以下代碼将建立一條消息,該消息會被發送給所在範圍内的其他裝置。

...public static final String TAG = "NfcActivity"; private static final String DATA_SPLITTER = "__:DATA:__"; private static final String MIME_TYPE = "application/my.applications.mimetype";
...
TelephonyManager tm = (TelephonyManager)Context.getSystemService(Context.TELEPHONY_SERVICE); String VERSION = tm.getDeviceSoftwareVersion();
...
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter == null) return;

String text = TAG + DATA_SPLITTER + VERSION; NdefRecord record = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, MIME_TYPE.getBytes(), new byte[0], text.getBytes()); NdefRecord[] records = { record }; NdefMessage msg = new NdefMessage(records); nfcAdapter.setNdefPushMessage(msg, this);
...
           

NFC 資料交換格式 (NDEF) 消息包含類型化資料、URI 或自定義應用程式負載。如果該消息包含與應用程式有關的資訊(如其名稱、MIME 類型或裝置軟體版本),則該資訊将被洩露給竊聽者。

解決方案:編寫錯誤消息時,始終要牢記安全性。在編碼的過程中,盡量避免使用繁複的消息,提倡使用簡短的錯誤消息。限制生成與存儲繁複的輸出資料将有助于管理者和程式員診斷問題的所在。此外,還要留意有關調試的跟蹤資訊,有時它可能出現在不明顯的位置(例如嵌入在錯誤頁 HTML 代碼的注釋行中)。

即便是并未揭示棧蹤迹或資料庫轉儲的簡短錯誤消息,也有可能幫助攻擊者發起攻擊。例如,“Access Denied”(拒絕通路)消息可以揭示系統中存在一個檔案或使用者。由于這個原因,它應始終保留資訊,而不是将其直接發送到程式外部的資源。

例 4:以下代碼僅在您的應用程式中廣播所捕獲到的異常的堆棧跟蹤,以便它不能洩露給系統中的其他應用程式。還有一個額外的好處,這比在系統中全局廣播更高效。

...
try {
...
} catch (Exception e) {
String exception = Log.getStackTraceString(e);
Intent i = new Intent();
i.setAction("SEND_EXCEPTION");
i.putExtra("exception", exception);
LocalBroadcastManager.getInstance(view.getContext()).sendBroadcast(i);
}
...
           

如果您擔心 Android 裝置上的系統資料會通過 NFC 洩露,那麼您可以采取以下三種措施之一。不把系統資料包括在發送到範圍内其他裝置的消息中,或加密消息負載,或在更高層中建立安全通信通道。

Tips:

1. 不要依賴于封裝器腳本、組織内部的 IT 政策或是思維靈活的系統管理者來避免 System Information Leak 漏洞。編寫安全的軟體才是關鍵。

2. 這類漏洞并不适用于所有類型的程式。例如,如果您在一個客戶機上執行應用程式,而攻擊者已經擷取了該客戶機上的系統資訊,或者如果您僅把系統資訊列印到一個可信賴的日志檔案中,就可以使用 AuditGuide 來過濾這一類别。

Path Manipulation

問題描述:當滿足以下兩個條件時,就會産生 path manipulation 錯誤:

1. 攻擊者能夠指定某一 file system 操作中所使用的路徑。 

2. 攻擊者可以通過指定特定資源來擷取某種權限,而這種權限在一般情況下是不可能獲得的。

例如,在某一程式中,攻擊者可以獲得特定的權限,以重寫指定的檔案或是在其控制的配置環境下運作程式。 

在這種情況下,攻擊者可以指定某個特定的數值進入 TarUtils.java 中第391 行的 entries(),這一數值可以通過 TarUtils.java 中第 396 行的FileOutputStream() 通路 file system 資源。

例 1: 下面的代碼使用來自于 HTTP 請求的輸入來建立一個檔案名。程式員沒有考慮到攻擊者可能使用像“../../tomcat/conf/server.xml”一樣的檔案名,進而導緻應用程式删除它自己的配置檔案。 

String rName = request.getParameter("reportName");
File rFile = new File("/usr/local/apfr/reports/" + rName);
...
rFile.delete();
           

例 2: 下面的代碼使用來自于配置檔案的輸入來決定打開哪個檔案,并傳回給使用者。如果程式在一定的權限下運作,且惡意使用者能夠篡改配置檔案,那麼他們可以通過程式讀取系統中以 .txt 擴充名結尾的所有檔案。 

fis = new FileInputStream(cfg.getProperty("sub")+".txt");
amt = fis.read(arr);
out.println(arr);
           

有些人認為在移動世界中,典型的漏洞(如 path manipulation)是無意義的 -- 為什麼使用者要攻擊自己?但是,謹記移動平台的本質是從各種來源下載下傳并在相同裝置上運作的應用程式。惡意軟體在銀行應用程式附近運作的可能性很高,它們會強制擴充移動應用程式的攻擊面(包括跨程序通信)。

例 3:以下代碼将例 1 改編為适用于 Android 平台。

...
String rName = this.getIntent().getExtras().getString("reportName");
File rFile = getBaseContext().getFileStreamPath(rName);
...
rFile.delete();
...
           

解決方案:防止 path manipulation 的最佳方法是采用一些間接手段:例如建立一份合法資源名的清單,并且規定使用者隻能選擇其中的檔案名。通過這種方法,使用者就不能直接由自己來指定資源的名稱了。 

但在某些情況下,這種方法并不可行,因為這樣一份合法資源名的清單過于龐大、難以跟蹤。是以,程式員通常在這種情況下采用黑名單的辦法。在輸入之前,黑名單會有選擇地拒絕或避免潛在的危險字元。但是,任何這樣一份黑名單都不可能是完整的,而且将随着時間的推移而過時。更好的方法是建立一份白名單,允許其中的字元出現在資源名稱中,且隻接受完全由這些被認可的字元組成的輸入。

Tips:

1. 如果程式正在執行輸入驗證,那麼您就應确信此驗證正确無誤,并使用 HPE Security Fortify Custom Rules Editor(HPE Security Fortify 自定義規則編輯器)為該驗證例程建立清理規則。

2. 執行本身有效的黑名單是一件非常困難的事情,是以,如果驗證邏輯完全依賴于黑名單方法,那麼有必要對這種邏輯進行質疑。鑒于不同類型的輸入編碼以及各種元字元集在不同的作業系統、資料庫或其他資源中可能有不同的含義,确定随着需求的不斷變化,黑名單能否友善、正确、完整地進行更新。

3. 許多現代 Web 架構都提供對使用者輸入執行驗證的機制。其中包括 Struts 和 Spring MVC。為了突出顯示未經驗證的輸入源,HPE Security Fortify 安全編碼規則包會降低 HPE Security Fortify Static Code Analyzer(HPE Security Fortify 靜态代碼分析器)報告的問題被利用的可能性,并在使用架構驗證機制時提供相應的依據,以動态重新調整問題優先級。我們将這種功能稱之為上下文敏感排序。為了進一步幫助 HPE Security Fortify 使用者執行審計過程,HPE Security Fortify 軟體安全研究團隊提供了資料驗證項目模闆,該模闆會根據應用于輸入源的驗證機制,将問題分組到多個檔案夾中。

Often Misused: Authentication

問題描述:許多 DNS 伺服器都很容易被攻擊者欺騙,是以應考慮到某天軟體有可能會在有問題的 DNS 伺服器環境下運作。如果允許攻擊者進行 DNS 更新(有時稱為 DNS 緩存中毒),則他們會通過自己的機器路由您的網絡流量,或者讓他們的 IP 位址看上去就在您的域中。勿将系統安全寄托在 DNS 名稱上。

在這種情況下,DNS 資訊通過RandomGUID.java 的第 55 行中的getLocalHost() 進入程式。

示例:以下代碼使用 DNS 查找,以确定輸入請求是否來自可信賴的主機。如果攻擊者可以攻擊 DNS 緩存,那麼他們就會獲得信任。

String ip = request.getRemoteAddr();
InetAddress addr = InetAddress.getByName(ip);
if (addr.getCanonicalHostName().endsWith("trustme.com")) {
trusted = true;
}
           

IP 位址相比 DNS 名稱而言更為可靠,但也還是可以被欺騙的。攻擊者可以輕易修改要發送的資料包的源 IP 位址,但是響應資料包會傳回到修改後的 IP 位址。為了看到響應的資料包,攻擊者需要在受害者機器與修改的 IP 位址之間截取網絡資料流。為實作這個目的,攻擊者通常會嘗試把自己的機器和受害者的機器部署在同一子網内。攻擊者可能會巧妙地采取源位址路由的方法來回避這一要求,但是在今天的網際網路上通常會禁止源位址路由。總而言之,核實 IP 位址是一種有用的 authentication 方式,但不應僅使用這一種方法進行 authentication。

解決方案:如果通過域名檢查的方式可以確定主機接受和發送的 DNS 記錄的一緻性,您可以更加信任這一方式。攻擊者如若不能控制目标域的域名伺服器,就無法同時欺騙接受和發送的 DNS 記錄。雖然這種方法并不簡單,但是:攻擊者也許可以說服域注冊者把域移交給一個惡意的域名伺服器。依賴于 DNS 記錄的 authentication 是有風險的。

雖然沒有十分簡單的 authentication 機制,但是還有比基于主機的 authentication 更好的方法。密碼系統提供了比較不錯的安全性,但是這種安全性卻易受密碼選擇不當、不安全的密碼傳送和 password management 失誤的影響。類似于 SSL 的方法值得考慮,但是通常這樣的方法過于複雜,以至于使用時會有運作出錯的風險,而關鍵資源也随時面臨着被竊取的危險。在大多數情況下,包括一個實體标記的多重 authentication 可以在合理的代價範圍内提供最大程度的安全保障。

Tips:

1. 檢查 DNS 資訊的使用情況。除了考慮程式員的 authentication 機制能否起作用以外,還應該考慮在社會工程攻擊中是如何利用 DNS 欺騙的。例如,如果攻擊者可以使自己發出的資料包看上去像是來自内部機器的,他們是否可以通過驗證程式獲得信任呢?

Insecure Transport: Mail Transmission

問題描述:通過未加密網絡發送的敏感資料容易被任何可攔截網絡通信的攻擊者讀取/修改。

解決方案:大多數現代郵件服務提供商提供了針對不同端口的加密備選方案,可使用 SSL/TLS 對通過網絡發送的所有資料進行加密,或者将現有的未加密連接配接更新到 SSL/TLS。如果可能,請始終使用這些備選方案

SQL Injection(TEST類)

問題描述:SQL injection 錯誤在以下情況下發生:

1. 資料從一個不可信賴的資料源進入程式。

在這種情況下,資料經由SuiteTestBase.java 的第 334 行進入executeQuery()。 

2. 資料用于動态地構造一個 SQL 查詢。 

這種情況下,資料被傳遞給SuiteTestBase.java 的第 334 行中的executeQuery()。

例 1:以下代碼動态地構造并執行了一個 SQL 查詢,該查詢可以搜尋與指定名稱相比對的項。該查詢僅會顯示條目所有者與被授予權限的目前使用者一緻的條目。 

...
String userName = ctx.getAuthenticatedUserName(); String itemName = request.getParameter("itemName"); String query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ itemName + "'"; ResultSet rs = stmt.execute(query);
...
           

這一代碼所執行的查詢遵循如下方式:

SELECT * FROM items
WHERE owner = <userName>
AND itemname = <itemName>;
           

但是,由于這個查詢是動态構造的,由一個不變的基查詢字元串和一個使用者輸入字元串連接配接而成,是以隻有在 itemName 不包含單引号字元時,才會正确執行這一查詢。如果一個使用者名為 wiley 的攻擊者為itemName 輸入字元串“name' OR 'a'='a”,那麼構造的查詢就會變成:

SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name' OR 'a'='a';
           

附加條件 OR 'a'='a' 會使 where 從句永遠評估為 true,是以該查詢在邏輯上将等同于一個更為簡化的查詢:

SELECT * FROM items;
           
這種查詢的簡化會使攻擊者繞過查詢隻傳回經過驗證的使用者所擁有的條目的要求;而現在的查詢則會直接傳回所有儲存在 items 表中的條目,不論它們的所有者是誰。
           

例 2:這個例子指出了不同的惡意數值傳遞給在例 1 中構造和執行的查詢時所帶來的各種影響。如果一個使用者名為 wiley 的攻擊者為 itemName輸入字元串“name'; DELETE FROM items; --”,那麼構造成的查詢語句将會變為兩個:

SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name';

DELETE FROM items;

--'
           

衆多資料庫伺服器,其中包括 Microsoft(R) SQL Server 2000,都可以一次性執行多條用分号分隔的 SQL 指令。對于那些不允許運作用分号分隔的批量指令的資料庫伺服器,比如 Oracle 和其他資料庫伺服器,攻擊者輸入的這個字元串隻會導緻錯誤;但是在那些支援這種操作的資料庫伺服器上,攻擊者可能會通過執行多條指令而在資料庫上執行任意指令。 

注意成對的連字元 (--);這在大多數資料庫伺服器上都表示下面的語句将作為注釋使用,而不能加以執行 [4]。在這種情況下,注釋字元的作用就是删除修改的查詢指令中遺留的最後一個單引号。而在那些不允許這樣加注注釋的資料庫中,通常攻擊者可以如例 1 那樣來攻擊。如果攻擊者輸入字元串“name'); DELETE FROM items; SELECT * FROM items WHERE 'a'='a”就會建立如下三個有效指令: 

SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name';

DELETE FROM items;

SELECT * FROM items WHERE 'a'='a';
           

有些人認為在移動世界中,典型的 Web 應用程式漏洞(如 SQL injection)是無意義的 -- 為什麼使用者要攻擊自己?但是,謹記移動平台的本質是從各種來源下載下傳并在相同裝置上運作的應用程式。惡意軟體在銀行應用程式附近運作的可能性很高,它們會強制擴充移動應用程式的攻擊面(包括跨程序通信)。

例 3:以下代碼将例 1 改編為适用于 Android 平台。

...
PasswordAuthentication pa = authenticator.getPasswordAuthentication(); String userName = pa.getUserName(); String itemName = this.getIntent().getExtras().getString("itemName"); String query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ itemName + "'"; SQLiteDatabase db = this.openOrCreateDatabase("DB", MODE_PRIVATE, null); Cursor c = db.rawQuery(query, null);
...
           

避免 SQL injection 攻擊的傳統方法之一是,把它作為一個輸入合法性檢查的問題來處理,隻接受列在白名單中的字元,或者識别并避免那些列在黑名單中的惡意資料。白名單方法是一種非常有效方法,它可以強制執行嚴格的輸入檢查規則,但是參數化的 SQL 指令所需維護更少,而且能提供更好的安全保障。而對于通常采用的列黑名單方式,由于總是存在一些小漏洞,是以并不能有效地防止 SQL injection 威脅。例如,攻擊者可以:

— 把沒有被黑名單引用的值作為目标

— 尋找方法以繞過對某一轉義序列元字元的需要

—使用存儲過程來隐藏注入的元字元

手動去除 SQL 查詢中的元字元有一定的幫助,但是并不能完全保護您的應用程式免受 SQL injection 攻擊。

防範 SQL injection 攻擊的另外一種常用方式是使用存儲過程。雖然存儲過程可以阻止某些類型的 SQL injection 攻擊,但是對于絕大多數攻擊仍無能為力。存儲過程有助于避免 SQL injection 的常用方式是限制可作為參數傳入的指令類型。但是,有許多方法都可以繞過這一限制,許多危險的表達式仍可以傳入存儲過程。是以再次強調,存儲過程在某些情況下可以避免這種攻擊,但是并不能完全保護您的應用系統抵禦 SQL injection 的攻擊。

解決方案:造成 SQL injection 攻擊的根本原因在于攻擊者可以改變 SQL 查詢的上下文,使程式員原本要作為資料解析的數值,被篡改為指令了。當構造一個 SQL 查詢時,程式員應當清楚,哪些輸入的資料将會成為指令的一部分,而哪些僅僅是作為資料。參數化 SQL 指令可以防止直接竄改上下文,避免幾乎所有的 SQL injection 攻擊。參數化 SQL 指令是用正常的 SQL 字元串構造的,但是當需要加入使用者輸入的資料時,它們就需要使用捆綁參數,這些捆綁參數是一些占位符,用來存放随後插入的資料。換言之,捆綁參數可以使程式員清楚地分辨資料庫中的資料,即其中有哪些輸入可以看作指令的一部分,哪些輸入可以看作資料。這樣,當程式準備執行某個指令時,它可以詳細地告知資料庫,每一個捆綁參數所使用的運作時的值,而不會被解析成對該指令的修改。

可以将例 1 改寫成使用參數化 SQL 指令(替代使用者輸入連續的字元串),如下所示:

...
String userName = ctx.getAuthenticatedUserName();
String itemName = request.getParameter("itemName");
String query = "SELECT * FROM items WHERE itemname=? AND owner=?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, itemName);
stmt.setString(2, userName);
ResultSet results = stmt.execute();
...
           

下面是 Android 的等同内容:

...
PasswordAuthentication pa = authenticator.getPasswordAuthentication();
String userName = pa.getUserName();
String itemName = this.getIntent().getExtras().getString("itemName");
String query = "SELECT * FROM items WHERE itemname=? AND owner=?";
SQLiteDatabase db = this.openOrCreateDatabase("DB", MODE_PRIVATE, null);
Cursor c = db.rawQuery(query, new Object[]{itemName, userName});
...
           

更加複雜的情況常常出現在報表生成代碼中,因為這時需要通過使用者輸入來改變 SQL 指令的指令結構,比如在 WHERE 條件子句中加入動态的限制條件。不要因為這一需求,就無條件地接受連續的使用者輸入,進而建立查詢語句字元串。當必須要根據使用者輸入來改變指令結構時,可以使用間接的方法來防止 SQL injection 攻擊:建立一個合法的字元串集合,使其對應于可能要加入到 SQL 指令中的不同元素。在構造一個指令時,可使用來自使用者的輸入,以便從應用程式控制的值集合中進行選擇。

Tips:

1. 使用參數化 SQL 指令的一個常見錯誤是使用由使用者控制的字元串來構造 SQL 指令。這顯然背離了使用參數化 SQL 指令的初衷。如果不能确定用來構造參數化指令的字元串是否由應用程式控制,請不要因為它們不會直接作為 SQL 指令執行,就假定它們是安全的。務必徹底地檢查 SQL 指令中使用的所有由使用者控制的字元串,確定它們不會修改查詢的含意。

2. 許多現代 Web 架構都提供對使用者輸入執行驗證的機制。其中包括 Struts 和 Spring MVC。為了突出顯示未經驗證的輸入源,HPE Security Fortify 安全編碼規則包會降低 HPE Security Fortify Static Code Analyzer(HPE Security Fortify 靜态代碼分析器)報告的問題被利用的可能性,并在使用架構驗證機制時提供相應的依據,以動态重新調整問題優先級。我們将這種功能稱之為上下文敏感排序。為了進一步幫助 HPE Security Fortify 使用者執行審計過程,HPE Security Fortify 軟體安全研究團隊提供了資料驗證項目模闆,該模闆會根據應用于輸入源的驗證機制,将問題分組到多個檔案夾中。

3. Fortify RTA adds protection against this category.

往期精彩

01 漫談發版哪些事,好課程推薦

02 Linux的常用最危險的指令

03 精講Spring&nbsp;Boot—入門+進階+執行個體

04 優秀的Java程式員必須了解的GC哪些

05 網際網路支付系統整體架構詳解

關注我

每天進步一點點

Fortify常見漏洞解決方案

很幹!在看嗎?☟