Android開發(fā)中關(guān)于組件導出的風險及防范
前言
近年來,移動APP存在一個非常的重要的問題就是安全問題,造成的后果有可能是用戶的隱私泄露和財產(chǎn)損失等,對于一款成熟的APP或者是金融銀行類APP,這無疑是最致命的,所以對APP進行有效的防范也是很有必要。
近段時間,公司安排了某安全公司對我們的APP進行了全方面的安全測試,根據(jù)文檔檢測結(jié)果看,整體上看還是很安全的,其中有一項就是組件導出風險,接下來我們說說四大組件、組件導出必要性、風險以及如何防范。
一、四大組件
從事Android開發(fā),我們都知道Android有四大組件, 分別是:
- 活動(Activity),用于表現(xiàn)功能,是用戶操作的可視化界面,它為用戶提供了一個完成操作指令的窗口;
- 服務(wù)(Service),后臺運行服務(wù),不提供界面呈現(xiàn);
- 廣播接受者(Broadcast Receive),用于接收廣播;
- 內(nèi)容提供者(Content Provider),支持多個應用中存儲和讀取數(shù)據(jù),相當于數(shù)據(jù)庫。
從這些組件簡單的介紹,我們知道它們的重要性,賦予了app更加豐富的功能,所以這四大組件的安全性對我們app和用戶來說就顯得更加地重要。
二、組件導出必要性
什么是組件導出呢?組件導出的意思就是組件可以被外部應用調(diào)用,我們可以在這四大組件聲明的清單文件設(shè)置組件是否導出,如下:
<activity
android:exported="true"
android:name=".other.ComponentActivity">
</activity>
或者:
<activity
android:name=".other.ComponentActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
</intent-filter>
</activity>
上面兩種方式都是Activity組件導出的方式,主要是exported的值",為true時表示導出,Activity中exported的默認值:
- 沒有intent filter時,默認為false;
- 有intent filter時,默認為true
Broadcast Receive和Service的默認值都跟Activity的一樣。
Content Provider中exported的默認值:
- 當minSdkVersion或者targetSdkVersion小于16時,默認為true
- 大于17時,默認為false
開發(fā)過程中,app會有一些特定需求會使用到三方SDK,如微信分享、支付、推送等功能,我們發(fā)現(xiàn)這里都有一個共同點,都會涉及到組件導出的問題,如微信的
WXEntryActivity:
<!-- 微信分享 -->
<activity
android:name="${applicationId}.wxapi.WXEntryActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
這樣就會被安全機構(gòu)檢測出來的,如果不設(shè)置WXEntryActivity為組件導出,微信分享等功能根本就調(diào)不起來,這是官方的寫法,我們認為這是必須要設(shè)置為組件導出,除非你把微信分享需求干掉,那業(yè)務(wù)不把你罵死;又或者是監(jiān)聽網(wǎng)絡(luò)變化的廣播接收器(7.0版本以上只能代碼中動態(tài)注冊才能接收該廣播)、推送功能,集成過一些推送SDK都有印象,一些Service也會聲明android:exported="true"等等。
這些無可避免的組件導出,我們可以回復安全機構(gòu):微信分享、推送等功能必須設(shè)置組件導出,所以我們只有保證自己的四大組件的設(shè)置,確保其是安全的,這樣才能確保app處于比較安全的狀態(tài),應付安全檢測,給你的領(lǐng)導一個交代。
三、組件導出風險
前面說明了組件的重要性、組件導出,那么組件導出的風險是什么呢?
- Activity作為組成Apk的四個組件之一,是Android程序與用戶交互的界面,如果Activity打開了導出權(quán)限,可能被系統(tǒng)或者第三方的App直接調(diào)出并使用。Activity導出可能導致登錄界面被繞過、拒絕服務(wù)攻擊、程序界面被第三方惡意調(diào)用等風險。
- Broadcast Receiver作為組成Apk的四個組件之一,對外部事件進行過濾接收,并根據(jù)消息內(nèi)容執(zhí)行響應,如果設(shè)置了導出權(quán)限,可能被系統(tǒng)或者第三方的App直接調(diào)出并使用。Broadcast Receiver導出可能導致敏感信息泄露、登錄界面被繞過等風險。S
- ervice作為組成Apk的四個組件之一,一般作為后臺運行的服務(wù)進程,如果設(shè)置了導出權(quán)限,可能被系統(tǒng)或者第三方的App直接調(diào)出并使用。Service導出可能導致拒絕服務(wù)攻擊,程序功能被第三方惡意調(diào)用等風險。
- Content Provider組成Apk的四個組件之一,是應用程序之間共享數(shù)據(jù)的容器,可以將應用程序的指定數(shù)據(jù)集提供給第三方的App,如果設(shè)置了導出權(quán)限,可能被系統(tǒng)或者第三方的App直接調(diào)出并使用。Content Provider導出可能導致程序內(nèi)部的敏感信息泄露,數(shù)據(jù)庫SQL注入等風險。
接下來以Activity導出為示例,說明下其風險,其它組件類比就好。首先Activity要在清單文件AndroidManifest.xml注冊:
<activity android:name="com.littlejerk.sample.other.WebActivity"/>
Activity的啟動通常有兩種方法
- 顯式啟動,需要指定啟動的Activity:
Intent intent = new Intent(getContext(),WebActivity.class);
intent.putExtra("URL","https://blog.csdn.net");
startActivity(intent);
- 隱式啟動,Intent中不再包含需要啟動的具體的Activity類,而是通過Intent提供某些信息,系統(tǒng)去檢索符合啟動意圖的Activity,這里是通過意圖過濾器聲明Intent信息:動作(action)、數(shù)據(jù)(data)、分類(Category)、類型(Type),組件(Component)、和擴展信息(Extra)。
<!-- 通過隱式啟動的方式需要在AndroidManifest.xml文件聲明-->
<activity android:name=".other.WebActivity">
<intent-filter>
<action android:name="com.littlejerk.sample.action.VIEW_URL" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
//調(diào)用方式啟動WebActivity
Intent intent = new Intent();
intent.setAction("com.littlejerk.sample.action.VIEW_URL");
intent.putExtra("URL","https://blog.csdn.net");
startActivity(intent);
使用Action跳轉(zhuǎn),如果有一個程序的AndroidManifest.xml中的某一個 Activity的IntentFilter段中 定義了包含了相同的Action,那么這個Intent就與這個目標Action匹配。如果這個IntentFilter段中沒有定義 Type、Category,那么這個 Activity就匹配了。但是如果手機中有兩個以上的程序匹配,那么就會彈出一個對話框來提示說明。
上面說過有IntentFilter,如果不指定android:exported,那么該值默認為true,外部的應用通過隱式意圖的方式也能將對應的組件啟動起來。這種情況我們就是我們說的組件導出,而導出則意味著很有可能存在安全問題,接下來看下WebActivity頁面:
Intent intent = getIntent();
String url = intent.getStringExtra("URL");
UILog.e(TAG, url.charAt(0));
mTvContent.setText(url);
我們注意到WebActivity只是接收一個URL并且顯示出來(沒有加載這個URL),從這里我們可以看出URL并沒有做參數(shù)檢驗,應用可能會崩潰;因為該頁面又是可被三方應用調(diào)用的,這時候如果別人惡意傳遞一些不良的網(wǎng)頁信息,那你這個應用不攔截就直接加載了,則這個應用有可能就要下架了。
四、如何防范
我們以最常見的Activity為例說明了組件導出的風險,因為這個URL參數(shù)是我們處理的,我們可以防止應用空指針異常,這沒問題,但是上面也說如果加載了不良URL呢?其實組件導出的風險最根本原因是被別人調(diào)用了,那這樣有沒有辦法控制這個別人的范圍,只允許我們信賴的人去調(diào)用。
在這里不得不提Android的權(quán)限機制,Android的Permission檢查機制是用來控制一個應用擁有哪些執(zhí)行權(quán)利。例如應用擁有拍照權(quán)限才能擁有拍照權(quán)利,那么我們是否可以通過權(quán)限來控制一個應用是否有啟動WebActivity的權(quán)利呢?
Android提供了自定義權(quán)限的能力,應用可以定義自己的權(quán)限,如在清單文件中自定義一個permission:
<permission
android:label="允許打開WebActivity頁面權(quán)限"
android:name="com.littlejerk.sample.permission.WEB"
android:protectionLevel="signature" />
label:權(quán)限的描述
name:該權(quán)限的名稱,使用該權(quán)限時通過名稱來指定使用的權(quán)限
protectionLevel:該權(quán)限受保護的等級,這很重要,它有三個等級
- signature:簽名級別權(quán)限,即權(quán)限的定義方和注冊方必須具有相同的簽名才有效
- system:系統(tǒng)級別權(quán)限,即權(quán)限的定義方和注冊方必須為系統(tǒng)應用
- signatureOrSystem :同簽名或系統(tǒng)應用,上述二者具備其一即可
權(quán)限定義完成,如何用它來保護暴露的組件呢,看下面代碼:
<!-- 通過隱式啟動的方式需要在AndroidManifest.xml文件聲明-->
<activity
android:permission="com.littlejerk.sample.permission.WEB"
android:name=".other.WebActivity">
<intent-filter>
<action android:name="com.littlejerk.sample.action.VIEW_URL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
在activity聲明時,activity標簽下有一個permission,通過permission就能指定保護該activity的權(quán)限名稱了,這樣,只有具有了該權(quán)限的activity才能啟動它(注意在定義方和使用方都要在清單文件中定義和聲明自定義的權(quán)限),在調(diào)用方的清單文件中聲明和使用該權(quán)限:
<!--調(diào)用方可不用聲明-->
<permission
android:label="允許打開WebActivity頁面權(quán)限"
android:name="com.littlejerk.sample.permission.WEB"
android:protectionLevel="signature" />
<!--調(diào)用方必須申請此權(quán)限-->
<uses-permission android:name="com.littlejerk.sample.permission.WEB"/>
有了權(quán)限的控制,activity組件導出的范圍就可控了,當我們公司應用間存在相互的組件調(diào)用時,就可以使用同簽名的權(quán)限來做限制,至于其它應用因為不是相同的簽名,所以它們無法調(diào)用我們暴露出去的組件,這很有效地規(guī)避了風險。
Activity是我們最常見的一個組件了,但是BroadcastReceiver用的地方也不少,一般安全評測都有提到這個組件的,我們有必要提一提它,其實各個組件的安全控制也可通過permission來控制的。
BroadcastReceiver的注冊有兩種方式
- 靜態(tài)注冊,在Manifest中聲明注冊
- 動態(tài)注冊,在代碼中依賴其他組件,通過registerReceiver注冊
BroadcastReceiver有廣播的發(fā)送方和接收方,所以當使用permission來校驗通信的時候一般都需要雙向校驗,即廣播的方送方和接收方都需要添加權(quán)限檢驗,保證發(fā)送方只將廣播發(fā)送給信賴的接收方,同樣的接收方也只接受來自信賴方的廣播。
廣播發(fā)送方
發(fā)送方需要在清單文件AndroidManifest.xml中聲明權(quán)限:
<permission
android:label="聲明發(fā)送方權(quán)限"
android:name="com.littlejerk.sample.permission.BROADCAST_SEND"
android:protectionLevel="signature" />
然后使用sendBroadcast(Intent intent, String receiverPermission)方法發(fā)送廣播:
//發(fā)送廣播
Intent intent = new Intent();
intent.setAction("com.littlejerk.sample.broadcast.action.TEST");
sendBroadcast(intent, "com.littlejerk.sample.permission.BROADCAST_SEND");
從receiverPermission字面意思就知道,接收廣播方必須要申請com.littlejerk.sample.permission.BROADCAST_SEND這個自定義權(quán)限,不然,無法接收到action通知,如接收方的清單文件AndroidManifest.xml:
<!-- 接收方需申請發(fā)送方權(quán)限-->
<uses-permission android:name="com.littlejerk.sample.permission.BROADCAST_SEND"/>
如果接收方的廣播接收器不控制自己的權(quán)限,則同開發(fā)者應用只監(jiān)聽com.littlejerk.sample.broadcast.action.TEST這個action就行了,但是為了雙重檢驗,我們也需要給接收方聲明自己的權(quán)限。
廣播接收方
我們定義一個廣播接收器TestReceiver:
public class TestReceiver extends BroadcastReceiver {
private static final String TAG = "TestReceiver";
//接收到廣播信息的回調(diào)
@Override
public void onReceive(Context context, Intent intent) {
//對外來的參數(shù)應該做些合法的檢查
String action = intent.getAction();
if (TextUtils.isEmpty(action)) {
return;
}
UILog.e(TAG, "action:" + action);
}
}
接著在清單文件AndroidManifest.xml中聲明控制權(quán)限:
<permission
android:label="聲明接收方權(quán)限"
android:name="com.littlejerk.sample.permission.BROADCAST_RECEIVER"
android:protectionLevel="signature" />
然后把這個控制權(quán)限給廣播接收器,接收器有兩種注冊方式
靜態(tài)注冊方式,在清單文件AndroidManifest.xml中:
<receiver
android:name=".widget.receiver.TestReceiver"
android:permission="com.littlejerk.sample.permission.BROADCAST_RECEIVER">
<intent-filter>
<action android:name="com.littlejerk.sample.broadcast.action.TEST"/>
</intent-filter>
</receiver>
然后是動態(tài)注冊方式,在你需要注冊的地方聲明:
Receiver receiver = new Receiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.littlejerk.sample.broadcast.action.TEST");
registerReceiver(receiver, intentFilter, "com.littlejerk.sample.permission.BROADCAST_RECEIVER", null);
這兩種注冊方式都可以,但是推薦使用動態(tài)注冊廣播的方式,因為Android O上為了App性能和功耗的考慮,對靜態(tài)注冊的廣播做了很大的限制,至于是什么限制,這里就不說了。
我們對接收器也做了權(quán)限限制,那么發(fā)送方也必須要申請這個權(quán)限才能發(fā)送action給它呀,所以發(fā)送方的清單文件AndroidManifest.xml中在原有的基礎(chǔ)上需要添加:
<!-- 發(fā)送方需申請接收方權(quán)限-->
<uses-permission android:name="com.littlejerk.sample.permission.BROADCAST_RECEIVER"/>
至此,廣播的雙向檢驗就完成了,以上所有代碼都測試過了,沒有任何問題,很好地過濾了無關(guān)廣播,保護了組件的安全。
總結(jié)
文章主要講了四大組件的含義及其重要性,然后闡明為什么會組件導出及導出風險,有些組件導出時必須的,因為要實現(xiàn)一些特定功能,但是對于可控的組件,盡量設(shè)置不導出。如果需要導出組件,我們需要嚴格地做參數(shù)檢驗,防止崩潰;除此之外,最好地防范就是添加權(quán)限,這樣才能有效地防止被惡意調(diào)用,造成不必要的損失。
到此這篇關(guān)于Android開發(fā)中關(guān)于組件導出的風險及防范的文章就介紹到這了,更多相關(guān)Android 組件導出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android 實現(xiàn)類似微信緩存和即時更新好友頭像示例
本篇文章主要介紹了android 實現(xiàn)類似微信緩存和即時更新好友頭像示例,具有一定的參考價值,有興趣的可以了解一下。2017-01-01
Android實現(xiàn)讀寫JSON數(shù)據(jù)的方法
這篇文章主要介紹了Android實現(xiàn)讀寫JSON數(shù)據(jù)的方法,以完整實例形式分析了Android解析及生成json數(shù)據(jù)的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
基于linux與windows平臺下 如何下載android sdk源代碼的方法詳解
本文主要是介紹在linux和windows平臺下,如何下載android sdk的源代碼,注意是sdk的源代碼,而不是android的所有源代碼,同時介紹如何把sdk源代碼加入到eclipse里,使android 平臺手機開發(fā)者可以直接查看源代碼,通過閱讀SDK源碼,能更好的理解和運用Android的API2013-05-05
Android端內(nèi)數(shù)據(jù)狀態(tài)同步方案VM-Mapping詳解
這篇文章主要介紹了Android端內(nèi)數(shù)據(jù)狀態(tài)同步方案VM-Mapping詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-09-09
Android 限制顯示小數(shù)點后兩位的實現(xiàn)方法
下面小編就為大家分享一篇Android 限制顯示小數(shù)點后兩位的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android 調(diào)用notifyDataSetChanged方法失敗解決辦法
這篇文章主要介紹了Android 調(diào)用notifyDataSetChanged方法失敗解決辦法的相關(guān)資料,需要的朋友可以參考下2017-07-07
21天學習android開發(fā)教程之SurfaceView與多線程的混搭
21天學習android開發(fā)教程之SurfaceView與多線程的混搭,感興趣的小伙伴們可以參考一下2016-02-02

