Android 應用之安全開發
一、引言
隨著移動互聯網向社會生活的各個領域滲透,APP 的使用越來越廣泛。但 Android 系統由于其開源的屬性,市場上針對開源代碼定制的 ROM 參差不齊(特別中國區域),在系統層面的安全防范和易損性都不一樣,Android 應用市場對 APP 的審核相對 iOS 來說也比較寬泛,市場上一些主流的 APP 雖然多少都做了一些安全防范,但由于大部分 APP 不涉及資金安全,所以對安全的重視程度不夠;而且由于安全是門系統學科,絕大部分 APP 層的開發人員缺乏對 APP 安全意識及措施,導致被有心者有機可乘。

Android 開發是當前最火的話題之一,但很少開發者會討論這個領域的安全問題,除了專業從業者,但移動應用安全隱患也給發展帶來了挑戰。
- 開發團隊通常將精力集中在產品設計、功能實現、用戶體驗和系統效率等方面,而很少考慮安全問題;
- 與一切都是集中管理的 iOS 相比,Android 提供了一種開放的環境,在獲得了靈活性、可以滿足各種定制需求的同時,也損失了部分安全性;
- Android 提供的安全機制比較復雜,開發者需要理解它們,并對常見的攻擊思路和攻擊方法有所了解,才能有效地保護軟件;
- 目前很少出現對特定移動軟件安全漏洞的大規模針對性攻擊,在真實的攻擊出現之前,許多人對此并不重視。
聲明,我不是專業的安全人員,從事的跟安全工作也沒有什么關系,本文從自已平時涉及的項目出發,對客戶端的代碼質量、代碼篡改、不安全的數據儲存、不安全的通信、不安全的認證、加密不足等安全問題作了說明,從普通開發者角度盡量去提高自已的 APP 安全,以降低代碼安全漏洞,減少代碼被利用的可能性,避免信任危機、經濟損失和法律風險。
二、移動應用面臨的安全問題

2.1 病毒
Android 病毒就是手機木馬,主要是一些惡意的應用程序。手機木馬有的獨立存在,有的則偽裝成圖片文件的方式附在正版 APP 上,隱蔽性極強,部分病毒還會出現變種,并且一代比一代更強大。
這些病毒有一些通用的特征:
- 母包 + 惡意子包的運行機制
- 通過技術手段防止用戶通過正常途徑卸載
- 以竊取用戶賬戶資金為目的
- 以短信作為指令通道
2.2 關鍵信息泄露(反編譯)
雖然 Java 代碼一般要做混淆,但是 Android 的幾大組件的創建方式是依賴注入的方式,因此不能被混淆,而且目前常用的一些反編譯工具比如 Apktool 等能夠毫不費勁的還原 Java 里的明文信息,Native 里的庫信息也可以通過 objdump 或 IDA 獲取。因此一旦 Java 或 Native 代碼里存在明文敏感信息,基本上是毫無安全而言的。
2.3 APP 重打包
即反編譯后重新加入惡意的代碼邏輯,從新打包一個 APK 文件。重打包的目的一般都是上面提到和病毒結合,對正版 APK 進行解包,插入惡意病毒后重新打包并發布,因此偽裝性很強。截住 APP 重打包就一定程度上防止了病毒的傳播。
2.4 進程被劫持
一般通過進程注入或者調試進程的方式來 Hook 進程,改變程序運行的邏輯和順序,獲取程序運行的內存信息,也就是用戶所有的行為都被監控起來,這也是盜取帳號密碼最常用的一種方式。
當然 Hook 行為不一定完全是惡意的,比如有些安全軟件會利用 Hook 的功能做主動防御。一般來說,Hook 需要獲取 root 權限或者跟被 Hook 進程相同的權限,因此如果你的手機沒有被 root,而且是正版 APK 的話,被注入還是很困難的。
2.5 數據在傳輸過程中遭劫持
傳輸過程最常見的劫持就是中間人攻擊。很多安全要求較高的應用程序要求所有的業務請求都是通過 Https,但是 Https 的中間人攻擊也逐漸多了起來,而且在實際使用中,證書交換和驗證在一些山寨手機或者非主流 ROM 上面存在一些問題,讓 Https 的使用碰到阻礙。
2.6 鍵盤輸入安全隱患
支付密碼一般是通過鍵盤輸入的,鍵盤輸入的安全直接影響了密碼的安全。鍵盤的安全隱患來自三個方面:
- 使用第三方輸入法,則所有的點擊事件在技術上都可以被三方輸入法截取,如果不小心使用了一些不合法的輸入法,或者輸入法把采集的信息上傳并且泄露,后果是不堪設想的。
- 截屏,該方法需要手機具有 root 權限,才能跑起截屏軟件
- getevent,通過讀取系統驅動層 dev/input/event1 中的信息,獲取手機觸屏的位置坐標,在結合鍵盤的布局,就能算出來事件跟具體數字的映射關系,這也是目前比較常用的攻擊方式
2.7 Webview 漏洞
由于現在 Hybrid APP 的盛行(混合開發),Webview 在 APP 的使用也是越來越多,Android 系統 Webview 存在一些漏洞,造成 js 提權。最為著名的就是傳說中 js 注入漏洞和 webkit xss 漏洞。
三、基礎安全
3.1 Manifest 文件安全
禁止 PermissionGroup 的屬性為空
PermissionGroup 可以對 permission 進行一個邏輯上的分組。如果 PermissionGroup 的屬性為空,會導致權限定義無效,且其他 APP 無法使用該權限。
開發建議:設置 PermissionGroup 屬性值或者不使用 PermissionGroup。
protectionLevel 屬性設置
由于對 APP 的自定義 permission 的 protectionLevel 屬性設置不當,會導致組件(如:content provider)數據泄露危險。最好的權限設置應為 signature 或signatureOrSystem ,進而避免被第三方應用利用。
開發建議:注意使用 signature 或 signatureOrSystem 防止其他 APP 注冊或接受該 APP 的消息,提高安全性。
合理設置 sharedUserId 權限
通過 sharedUserId,可以讓擁有同一個 User Id 的多個 APP 運行在同一個進程中,互相訪問任意資源。將 sharedUserId 設置為 android.uid.system,可以把 APP 放到系統進程中,APP 將獲得極大的權限。如果 APP 同時有 master key 漏洞,容易導致被 root。
開發建議:合理設置軟件權限。
設置 allowBackup 為 false
當這個標志被設置成 true 或不設置該標志位時,應用程序數據可以備份和恢復,adb 調試備份允許惡意攻擊者復制應用程序數據。
開發建議:設置 AndroidManifest.xml 的 android:allowBackup 標志為 false。
禁止 Debuggable 為 true
在 AndroidManifest.xml 中定義 Debuggable 項,如果該項被打開,APP 存在被惡意程序調試的風險,可能導致泄露敏感信息等問題。
開發建議:顯示的設置 AndroidManifest.xml 的 debuggable 標志為 false。
3.2 組件安全
合理設置導出Activity、activity-alias、service、receiver
Activity、activity-alias、service、receiver 組件對外暴露會導致數據泄露和惡意的攻擊。
開發建議:最小化組件暴露。對不會參與跨應用調用的組件添加 android:exported=false 屬性。
設置組件訪問權限。對跨應用間調用的組件或者公開的 receiver、service、activity 和 activity-alias 設置權限,同時將權限的 protectionLevel 設置為 signature 或 signatureOrSystem。組件傳輸數據驗證。對組件之間,特別是跨應用的組件之間的數據傳入與返回做驗證和增加異常處理,防止惡意調試數據傳入,更要防止敏感數據返回。
使用顯式 Intent 調用 bindService()
創建隱式 Intent 時,Android 系統通過將 Intent 的內容與在設備上其他應用的清單文件中聲明的 Intent 過濾器進行比較,從而找到要啟動的相應組件。如果 Intent 與 Intent 過濾器匹配,則系統將啟動該組件,并將其傳遞給對象。如果多個 Intent 過濾器兼容,則系統會顯示一個對話框,支持用戶選取要使用的應用。
為了確保應用的安全性,啟動 Service 時,請始終使用顯式 Intent,且不要為服務聲明 Intent 過濾器。使用隱式 Intent 啟動服務存在安全隱患,因為您無法確定哪些服務將響應 Intent,且用戶無法看到哪些服務已啟動。從 Android 5.0(API 級別 21)開始,如果使用隱式 Intent 調用 bindService(),系統會拋出異常。
開發建議:為了確保應用的安全性,啟動 Service 時,請始終使用顯式 Intent,且不要為服務聲明 Intent 過濾器。使用隱式 Intent 啟動服務存在安全隱患,因為您無法確定哪些服務將響應 Intent,且用戶無法看到哪些服務已啟動。從 Android 5.0(API 級別 21)開始,如果使用隱式 Intent 調用 bindService(),系統會拋出異常。
影響范圍
全部。從 Android 5.0(API 級別 21)開始,如果使用隱式 Intent 調用 bindService(),系統會拋出異常。
合理處理 Intent Scheme URL
Intent Scheme URI 是一種特殊的 URL 格式,用來通過 Web 頁面啟動已安裝應用的 Activity 組件,大多數主流瀏覽器都支持此功能。
Android Browser 的攻擊手段—— Intent Scheme URLs 攻擊。這種攻擊方式利用了瀏覽器保護措施的不足,通過瀏覽器作為橋梁間接實現 Intend-Based 攻擊。相比于普通 Intend-Based 攻擊,這種方式極具隱蔽性,
如果在 APP 中,沒有檢查獲取到的 load_url 的值,攻擊者可以構造釣魚網站,誘導用戶點擊加載,就可以盜取用戶信息。所以,對 Intent URI 的處理不當時,就會導致基于 Intent 的攻擊。
如果瀏覽器支持 Intent Scheme URI 語法,一般會分三個步驟進行處理:
- 利用 parseUri 解析 uri,獲取原始的 intent 對象;
- 對 intent 對象設置過濾規則;
- 通過 startActivityIfNeeded 或者 startActivity 發送 intent;其中步驟2 起關鍵作用,過濾規則缺失或者存在缺陷都會導致 Intent Schem URL 攻擊。
關鍵點
Intent.parseUri 函數,通過掃描出所有調用了 Intent.parseUri 方法的路徑,并檢測是否使用如下的策略。
比較安全的使用 Intent Scheme URI 方法是:
如果使用了 Intent.parseUri 函數,獲取的 intent 必須嚴格過濾,intent 至少包含 addCategory(android.intent.category.BROWSABLE),setComponent(null),setSelector(null)3 個策略。
所以,在檢的時候只要根據 Intent.parseUri 函數返回的 Intent 對象有沒有按照以下方式實現即可做出判斷:
// convert intent scheme URL to intent objectIntent intent = Intent.parseUri(uri);// forbid launching activities without BROWSABLE categoryintent.addCategory(android.intent.category.BROWSABLE);// forbid explicit callintent.setComponent(null);// forbid intent with selector intent intent.setSelector(null);// start the activity by the intentcontext.startActivityIfNeeded(intent, -1)
開發建議:如果使用了 Intent.parseUri 函數,獲取的 intent 必須嚴格過濾,intent 至少包含 addCategory(android.intent.category.BROWSABLE),setComponent(null),setSelector(null)3 個策略。除了以上做法,最佳處理不要信任任何來自網頁端的任何 intent,為了安全起見,使用網頁傳過來的 intent 時,要進行過濾和檢查。
本地拒絕服務
Android 系統提供了 Activity、Service 和 Broadcast Receiver 等組件,并提供了 Intent 機制來協助應用間的交互與通訊,Intent 負責對應用中一次操作的動作、動作涉及數據、附加數據進行描述,Android 系統則根據此 Intent 的描述,負責找到對應的組件,將 Intent 傳遞給調用的組件,并完成組件的調用。Android 應用本地拒絕服務漏洞源于程序沒有對 Intent.GetXXXExtra() 獲取的異常或者畸形數據處理時沒有進行異常捕獲,從而導致攻擊者可通過向受害者應用發送此類空數據、異常或者畸形數據來達到使該應用 Crash 的目的,簡單的說就是攻擊者通過 Intent 發送空數據、異常或畸形數據給受害者應用,導致其崩潰。
對導出的組件傳遞一個不存在的序列化對象,若沒有 try…catch 捕獲異常就會崩潰
ComponentName cn = new ComponentName(com.test, com.test.TargetActivity)Intent i = new Intent()i.setComponentName(cn)i.putExtra(key, new CustomSeriable())
startActivity(i)public class DataSchema implements Serializable {
public DataSchema() {
super();
}
}
NullPointerException 異常導致的拒絕服務
源于程序沒有對 getAction() 等獲取到的數據進行空指針判斷,從而導致了空指針異常導致應用崩潰
風險代碼:
Intent i = new Intent();if (i.getAction().equals(TestForNullPointerException)) { Log.d(TAG, Test for Android Refuse Service Bug);
}
ClassCastException 異常導致的拒絕服務
源于程序沒有對 getSerializableExtra() 等獲取到的數據進行類型判斷而進行強制類型轉換,從而導致類型轉換異常導致拒絕服務漏洞
風險代碼:
Intent i = getIntent();String test = (String) i.getSerializableExtra(serializable\_key);
IndexOutOfBoundsException 異常導致拒絕服務漏洞
源于程序沒有對 getIntegerArrayListExtra() 等獲取到的數據數組元素大小判斷,導致數組訪問越界而造成拒絕服務漏洞
風險代碼:
Intent intent = getIntent();
ArrayList intArray = intent.getIntegerArrayListExtra(user\_id);if (intArray != null) { for (int i = 0; i < 10; i++) { intArray.get(i);
}
}
ClassNotFoundException 異常導致的拒絕服務漏洞
Intent i = getIntent();getSerializableExtra(key);
開發建議:將比不要導出的組建設置為不導出
在處理 Intent 數據時,進行捕獲異常,通過 getXXXExtra() 獲取的數據時進行以下判斷,以及用 try catch 方式捕獲所有異常,防止出現拒絕服務漏洞,包括:空指針異常、類型轉換異常、數組越界訪問異常、類未定義異常、其他異常
Try{
....
xxx.getXXXExtra()
....
}Catch Exception{
** ** **// 為空即可**}
刪除 Debug 和 Test 信息
一些 APP 在正式發布前,為了方便調試 APP,都會在 APP 里集成一些調試或測試界面。這些測試界面可能包含敏感的信息。
四、反調試 & 反篡改
從 Android APP 的結構來說,dex 文件是最重要、最需要保護的,因為 dex 中存放了代碼的信息,開發者通過使用 dex2jar 和 jd-gui 簡單幾步就可以查看到源碼。
Andriod 應用程序使用 Java 開發,可通過反編譯的方式獲取對應的源碼
- APK 包其實就是個 ZIP 包,用 WinRAR 解開獲得 classes dex classes.dex
- 使用 dex2jar 將程序轉換成 jar 文件
- 使用 jad 對 jar 文件進行反編譯
ProGuard 是一個免費的 Java 類文件的壓縮,優化,混肴器
新建個 Android 工程之后,proguard.cfg 文件會在工程的根目錄下自動創建文件定義了混淆器是怎樣優化和混淆你的代碼
目前開發常用方法:
- 使用混淆保護,對 APK 代碼進行基礎的防護;
- 加殼,dump 出 dex 對于大多數人來說依然是一件非常困難的事;
- 反二次打包,可以通過在原生層驗證簽名來實現(其代碼在 Java 層);
- 處理編譯后的二進制 AndroidManifest.xml 文件,添加無效的參數,使反編譯得到錯誤的清單文件;
- 第三方平臺:愛加密(加殼技術,對 dex 文件做了一層保護殼,),360 加固等。
五、數據安全
5.1 SQLite 安全
SQLite sql 注入漏洞
SQLite 做為 android 平臺的數據庫,對于數據庫查詢,如果開發者采用字符串鏈接方式構造 SQL 語句,就會產生 SQL 注入。
開發建議:
- provider 不需要導出,請將 export 屬性設置為 false
- 若導出僅為內部通信使用,則設置 protectionLevel=signature
- 不直接使用傳入的查詢語句用于 projection 和 selection,使用由 query 綁定的參數 selectionArgs
- 完備的 SQL 注入語句檢測邏輯
Databases 任意讀寫漏洞
APP 在使用 openOrCreateDatabase 創建數據庫時,將數據庫設置了全局的可讀權限,攻擊者惡意讀取數據庫內容,獲取敏感信息。在設置數據庫屬性時如果設置全局可寫,攻擊者可能會篡改、偽造內容,可以能會進行詐騙等行為,造成用戶財產損失。
開發建議
- 用 MODE_PRIVATE 模式創建數據庫
- 使用 sqlcipher 等工具加密數據庫
- 避免在數據庫中存儲明文和敏感信息
5.2 剪貼板敏感信息泄露風險
由于 Android 剪貼板的內容向任何權限的 APP 開放,很容易就被嗅探泄密。同一部手機中安裝的其他 APP,甚至是一些權限不高的 APP,都可以通過剪貼板功能獲取剪貼板中的敏感信息。
風險代碼:
clipBtn = (Button) findViewById(R.id.btn\_clip);
clipBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD\_SERVICE);
ClipData clip1 = ClipData.newPlainText(label,password=123456);
clipboard.setPrimaryClip(clip1);
}
});
開發建議:避免使用剪貼板敏文存儲敏感信息或進行加密
5.3 密鑰硬編碼風險
在代碼中禁止硬編碼私鑰等敏感信息,攻擊者反編譯代碼,即可拿到。
5.4 Intent敏感數據泄露
APP 創建 Intent 傳遞數據到其他 Activity,如果創建的 Activity 不是在同一個 Task 中打開,就很可能被其他的 Activity 劫持讀取到 Intent 內容,跨 Task 的Activity 通過 Intent 傳遞敏感信息是不安全的。
開發建議:盡量避免使用包含 FLAG_ACTIVITY_NEW_TASK 標志的 Intent 來傳遞敏感信息。
5.5 PendingIntent 誤用風險
使用 pendingIntent 時候,如果使用了一個空 Intent,會導致惡意用戶劫持 Intent 的內容。禁止使用空 intent 去構造 pendingIntent。
開發建議:禁止使用空 intent 去構造 pendingIntent。
5.6 數據或程序(DEX、SO)加載、刪除檢查
程序在加載外部 dex、so 文件是否判斷文件來源、是否存放可信區域;程序刪除文件是否可篡改文件路勁
是否加載公共區域程序,如 sdcard、/data/local/tmp/、應用自創建但其他應用有讀寫權限的目錄上
是否從網絡下載,檢測方法包括:閱讀代碼、監聽網路請求、見識存儲區域文件讀寫、查看安裝包
升級包是否存在公共區域存儲。
5.7 文件全局讀寫漏洞
在使用 getDir、getSharedPreferences(SharedPreference) 或 openFileOutput 時,如果設置了全局的可讀權限,攻擊者惡意讀取文件內容,獲取敏感信息。在設置文件屬性時如果設置全局可寫,攻擊者可能會篡改、偽造內容,可能會進行詐騙等行為,造成用戶財產損失。其中 getSharedPreferences 如果設置全局寫權限,則當攻擊 APP 跟被攻擊 APP 具有相同的 Android:sharedUserId 屬性時和簽名時,攻擊 APP 則可以訪問到內部存儲文件進行寫入操作。
開發建議
使用 MODE_PRIVATE 模式創建內部存儲文件
加密存儲敏感數據
避免在文件中存儲明文敏感信息
避免濫用 Android:sharedUserId 屬性
如果兩個 APP Android:sharedUserId 屬性相同,切使用的簽名也相同,則這兩個 APP 可以互相訪問內部存儲文件數據
5.8 日志泄露風險
在 APP 的開發過程中,為了方便調試,通常會使用 log 函數輸出一些關鍵流程的信息,這些信息中通常會包含敏感內容,如執行流程、明文的用戶名密碼等,這會讓攻擊者更加容易的了解 APP 內部結構方便破解和攻擊,甚至直接獲取到有價值的敏感信息。
開發建議: 禁止打印敏感信息
六、Webview 安全
6.1 Webview組件安全
WebView 組件中的接口函數 addJavascriptInterface 存在遠程代碼執行漏洞,遠程攻擊者利用此漏洞能實現本地 Java 和 js 的交互,可對 Android 移動終端進行網頁掛馬從而控制受影響設備。
6.2 Webview API接口是否進行白名單限制
Webview 接口避免使用第三方程序惡意使用發送短信,撥打電話,刪除文件
修復方案:白名單進行限制,功能僅限于該應用的功能范圍之內
6.3 WebView 釣魚漏洞
釣魚這個事一直都安全界最常用的攻擊,也是最難通過技術手段解決的一類問題,而且釣魚的手段也是千奇百怪,要防釣魚除了讓用戶提高安全意識,不點擊來路不明的鏈接外,
技術層面可以做到如下兩點:
- 檢查 WebView 加載目標URL是否存在釣魚欺騙等安全風險
- 對 Webview 關閉腳本環境
- WebView 跨域漏洞
主要是由于 JS 的 XmlHttpRequest 可以讀取本地文件,從而讀取到 APP data 數據庫目錄下的 webviewCookiesChromium.db , 這個 db 通常是系統存放 cookie 的地方,相當于變相的為讀取 cookie 開了權限。
七、弱加密風險檢測
7.1 禁止使用弱加密算法
安全性要求高的應用程序必須避免使用不安全的或者強度弱的加密算法,現代計算機的計算能力使得攻擊者通過暴力破解可以攻破強度弱的算法。例如,數據加密標準算法 DES(密鑰默認是 56 位長度、算法半公開、迭代次數少)是極度不安全的,使用類似 EFF(Electronic Frontier Foundaton)Deep Crack 的計算機在一天內可以暴力破解由 DES 加密的消息。
開發建議:建議使用安全性更高的 AES 加密算法
7.2 不安全的密鑰長度風險
在使用 RSA 加密時,密鑰長度小于 512bit,小于 512 bit 的密鑰很容易被破解,計算出密鑰。
風險代碼:
public static KeyPair getRSAKey() throws NoSuchAlgorithmException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
keyGen.initialize(512); KeyPair key = keyGen.generateKeyPair(); return key;
}
開發建議:使用 RSA 加密時,建議密鑰長度大于 1024bit
7.3 AES/DES弱加密風險(ECB)
AES 的 ECB 加密模式容易遭到字典攻擊,安全性不夠。
風險代碼:
SecretKeySpec key = new SecretKeySpec(keyBytes, AES);
Cipher cipher = Cipher.getInstance(AES/ECB/PKCS7Padding, BC);
cipher.init(Cipher.ENCRYPT\_MODE, key);
開發建議:避免使用 ECB 模式,建議使用 CBC。
7.4 IVParameterSpec不安全初始化向量
使用 IVParameterSpec 函數,如果使用了固定的初始化向量,那么密碼文本可預測性高得多,容易受到字典攻擊等。
風險代碼:
byte[] iv = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };IvParameterSpec ips = new IvParameterSpec(iv)
復制代碼開發建議
IVParameterSpec 初始化時,不使用常量 vector。
7.5 KeyStore 弱密碼風險
keytool 是一個 Java 數據證書的管理工具,Keytool 將密鑰(key,私鑰和公鑰配對)和證書(certificates)存在一個稱為 keystore 的文件中,并通過密碼保護 keystore 中的密鑰。如果密碼設置過于簡單,例如:123456、android 等,則會導致 keystore 文件的私鑰泄露,從而導致一系列的信息泄露風險。
開發建議:提高 keystore 保護密碼的強度
八、其他風險
8.1 謹慎使用高風險函數
在程序需要執行系統命令等函數,需要謹慎使用,嚴格控制命令來源,防止黑客替換命令攻擊。
開發建議:嚴格按照要求使用
8.2 重要函數邏輯安全
程序中重要的邏輯函數建議使用 NDK 技術通過 c/c++ 代碼實現。
因為 APK 本身未進行專業加固保護,存在被 baksmali/apktool/dex2jar 直接反編譯獲取程序 Java 代碼的風險,建議程序的重要函數使用 Android ndk 技術通過 c/c++ 實現,將重要函數編譯到 so 庫中,能夠提高重要函數的邏輯安全強度。
8.3 發布版本需加固
發布的軟件,應對 APP 進行加固,防止攻擊者獲取 APP 代碼、業務邏輯、API 接口等,對業務和公司聲譽造成一定影響,防止 APP 被破解二次打包,導致損失。
開發建議:APP 加固