Fortify掃描漏洞解決方案:
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.