Android LaunchAnywhere组件权限绕过漏洞
Posted Tr0e
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android LaunchAnywhere组件权限绕过漏洞相关的知识,希望对你有一定的参考价值。
文章目录
前言
android APP 应用的攻击面多数集中在对外暴露(exported="true”)的四大组件(Activity、Service、ContentProvider、BroadcastReceiver)上,当组件设置 exported=“false” 或者是添加了权限保护的情况下,三方应用程序无法直接访问该组件,也就很难去借助该类组件攻击 APP。
但是对于系统应用(具有系统签名、uid = system,所以具备行使 system 的权力)而言,它们是往往具有无视组件 exported=“false” 属性的能力的,可想而知,如果此类 app 存在可以被攻击者控制的漏洞的话,将使得攻击者 APP 也获得访问系统任意 APP 组件的能力,即本文想要讨论的 LaunchAnywhere。
Android组件调用权限检查
在 Android 中,可以说 System 用户拥有相当高的权限,通过阅读源码可以发现,所有 permissoin 检查的地方都是直接放行 System 用户的,具体见代码 ActivityManagerService.checkComponentPermission :
通过源码可以看到,对于 System 用户可以完全无视权限检查,不管组件是否为 exported=true,直接返回PERMISSION_GRANTED
。
LaunchAnyWhere
Google 曾经修复了一个组件安全的漏洞 LaunchAnyWhere(Google Bug 7699048)。这个漏洞属于 Intend Based 提取漏洞,攻击者利用这个漏洞,可以突破了应用间的权限隔离,达到调用任意私有 Activity(exported=false)的目的。该漏洞影响 Android 2.3 至 4.3 固件。
Account 管理机制
从 Android2.0 开始,系统引入了 Account 管理机制,详细使用说明见 Android 官方文档。Account 管理机制提供了集中化管理帐户 API 以及安全存储用户口令和令牌的功能。在系统中,可以同时存在多个帐户(可通过”设置-添加帐户”可以查看),比如 Google、Miscrosoft Exchange、微信、支付宝、陌陌等等。
如果想要出现在这个页面里,应用需要声明一个账户认证服务 AuthenticationService:
<service
android:name=".authenticator.AuthenticationService"
android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
历史漏洞原理分析
普通应用(记为 AppA)去请求添加某类账户时,会调用 AccountManager.addAccount,然后 AccountManager 会去查找提供账号的应用(记为 AppB)的 Authenticator 类,调用 Authenticator. addAccount 方法;AppA 再根据 AppB 返回的 Intent 去调起 AppB 的账户登录界面。
具体的代码,AccountManager.addAccount:
注意到 addAccount 函数最后执行一个 AmsTask 的异步任务,mRespone 是一个 Binder 对象,当 AuthenticationService 指定 Intent 后,就是把 Intent 保存到这个 respone 对象里,然后在 Response 中直接调用 startActivity:
我们可以将这个流程转化为一个比较简单的事实:
- AppA 请求添加一个特定类型的网络账号;
- 系统查询到 AppB 可以提供一个该类型的网络账号服务,系统向 AppB 发起请求;
- AppB 返回了一个 intent 给系统,系统把 intent 转发给 appA;
- AccountManagerResponse 在 AppA 的进程空间内调用 startActivity(intent) 调起一个 Activity,AccountManagerResponse 是 FrameWork 中的代码, AppA 对这一调用毫不知情。
这种设计的本意是,AccountManager Service 帮助 AppA 查找到 AppB 账号登陆页面,并呼起这个登陆页面。而问题在于,AppB 可以任意指定这个 intent 所指向的组件,AppA 将在不知情的情况下由AccountManagerResponse 调用起了一个 Activity。如果 AppA 是一个 system 权限应用(比如Settings),那么 AppA 能够调用起任意 AppB 指定的未导出 Activity。
而为了指定拉起任意组件,Step 3 中 AppB 返回 bundle 的代码可以如下:
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
String authTokenType, String[] requiredFeatures, Bundle options)
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.trick.trick ",
" com.trick. trick.AnyWhereActivity"));
intent.setAction(Intent.ACTION_RUN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
如何利用上述漏洞?
上文已经提到过,如果假设 AppA 是Settings,AppB 是攻击程序。那么只要能让 Settings 触发 addAcount 的操作,就能够让 AppB launchAnyWhere。而问题是,怎么才能让 Settings 触发添加账户呢?
如果从“设置->添加账户”的页面去触发,则需要用户手工点击才能触发,这样攻击的成功率将大大降低,因为一般用户是很少从这里添加账户的,用户往往习惯直接从应用本身登录。不过现在就放弃还太早,其实 Settings 早已经给我们留下触发接口。只要我们调用 com.android.settings.accounts.AddAccountSettings
,并给 Intent 带上特定的参数,即可让 Settings 触发 launchAnyWhere:
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[] = Constants.ACCOUNT_TYPE;
intent1.putExtra("account_types", authTypes);
AuthenticatorActivity.this.startActivity(intent1);
漏洞利用的流程示意图如下:
漏洞的利用与防御
主要的攻击对象还是应用中未导出的 Activity,特别是包含了一些 intenExtra 的 Activity。
比如绕过手机 pin 码原密码的认证界面,直接拉起输入新密码的 Activity 从而直接重置手机系统 pin 码:
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;
即借助 Setting 拉起如下页面:
Android 历史上也还发生过另外一种可以以上拉起 Pin 码重置页面的漏洞,参见 [翻译]Android框架层漏洞-Fragment注入,漏洞太古老且没多大参考价值,此处不展开。
漏洞的修复
这个漏洞在 4.4 上已经修复,看看修复的代码,可以找到防御的思路:
public void onResult(Bundle result)
mNumResults++;
- if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN)))
+ Intent intent = null;
+ if (result != null
+ && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null)
+ /*
+ * The Authenticator API allows third party authenticators to
+ * supply arbitrary intents to other apps that they can run,
+ * this can be very bad when those apps are in the system like
+ * the System Settings.
+ */
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+ int authenticatorUid = Binder.getCallingUid();
+ if (PackageManager.SIGNATURE_MATCH !=
+ pm.checkSignatures(authenticatorUid, targetUid))
+ throw new SecurityException(
+ "Activity to be started with KEY_INTENT must " +
+ "share Authenticator's signatures");
+
+
+ if (result != null
+ && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN)))
String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType))
@@ -2223,6 +2276,7 @@
super(looper);
由于 Resopne 是一个 Binder 对象,因此当 onResult 被回调时,可以通过 Binder.getCallingUid() 获取 authenticatorUid,如果 targetUid 跟 authenticatorUid 不相同,则直接对 AuthenticationService 抛异常。
BroadcastAnywhere
与 LaunchAnywhere 原理相似,通过这个漏洞,攻击者可以无视 BroadcastReceiver 组件访问限制,以 system 用户的身份发送广播。
与 LaunchAnywhere 相比,这两个漏洞的相同点在于:
- 都是利用了 addAccount 这个机制,一个恶意 app 通过注册为 account 的 authenticator 并处理某账号类型,然后发送 intent 给 settings app,让其添加该特定类型的账号。
- 都是利用 settings 这个应用具有 SYSTEM 权限,诱使 settings 来发送一个高权限的 intent。
两个漏洞的不同点在于:
- 本质原理不同:一个是恶意 app 返回一个 intent 被 settings launch,另外一个是 settings 发出一个 pendingintent 给恶意 app,而恶意 app 利用 pendingintent 的特点来修改 pendingitent 的 action 与 extras,并以 settings 的身份发出。
- 漏洞代码位置不同:一个是 accountmanger 中,一个是 settings 中;
- 后果不同:launchAnywhere 是以 system 权限启动 activity,而 broadcastAnywhere 是一个 system 权限发送 broadcast,前者往往需要界面,而后者不需要界面。
漏洞具体原理分析
关于 PendingIntent,简单理解是一种异步发送的 intent,通常被使用在通知 Notification 的回调,短消息 SmsManager 的回调和警报器 AlarmManager 的执行等等,是一种使用非常广的机制。其具体使用和威胁可以参见我的另一篇博文:PendingIntent劫持导致app任意文件读写漏洞。
PendingIntent 的安全风险主要发生在下面两个条件同时满足的场景下:
- 构造 PendingIntent 时的原始 Intent 既没有指定 Component,也没有指定 action;
- 将 PendingIntent 泄露给第三方。
如果原始 Intent 的 Component 与 action 都为空(“双无”Intent),B 就可以通过修改 action 来将 Intent 发送向那些声明了 intent filter 的组件,如果 A 是一个有高权限的 APP(如 settings 就具有 SYSTEM 权限),B 就可以以 A 的身份做很多事情。
在Android 4.4 版本的 settings 系统应用 AddAccountSettings 类的 addAccount 函数:
可见一个 mPendingIntent 是通过 new Intent() 构造原始 Intent 的,所以为“双无” Intent,这个 PendingIntent 最终被通过 AccountManager.addAccount 方法传递给了恶意 APP 。
漏洞利用
最初报告这个漏洞给 Android 时,用的伪造短信的 POC,例如可以伪造 10086 发送的短信,这与收到正常短信的表象完全一致。后来又更新了一个 Factory Reset 的 POC,可以强制无任何提示将用户手机恢复到出厂设置,清空短信与通信录等用户数据,恶意 APP 的接口代码片段如下:
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException
//这里通过getParcelable(“pendingintent”)就获得了settings传过来的“双无”PendingIntent:
PendingIntent test = (PendingIntent)options.getParcelable("pendingIntent");
Intent newIntent2 = new Intent("android.intent.action.MASTER_CLEAR");
try
test.send(mContext, 0, newIntent2, null, null);
catch (CanceledException e)
e.printStackTrace();
事实上可利用的广播实在太多了,比如:
- 发送
android.provider.Telephony.SMS_DELIVER
能够伪造接收短信; - 发送
android.intent.action.ACTION_SHUTDOWN
能够直接关机; - 发送
android.intent.action.MASTER_CLEAR
广播,设备将恢复至出厂设置。
攻击者通过漏洞能够伪造亲朋好友或者银行电商的短信。跟正常的短信全然无异,普通用户根本无法甄别。除了伪造短信外,攻击者能够利用该漏洞恢复出厂设置等。
漏洞官方修复方案
在 Android 5.0 的源码中,修复方法是设置了一个虚构的 Action 与 Component,参见 Android源码:
总结
总结下上述两个漏洞的根因:
- LaunchAnywhere 漏洞:在于系统服务 AccountManager Server “自作主张”调用 StartActivity 以 Settings 系统应用的进程身份帮助其拉起了攻击者可控的 Intent,最终导致攻击者可以借助系统应用 Settings 的权限来拉起任意组件;
- BroadcastReceiver 漏洞:在于系统应用 Settings 发送一个未设置 action/componnent 的 PendingIntent 给到三方应用程序,最终导致攻击者可以借助系统应用 Settings 的权限来发送任意广播。
两个漏洞给我们安全测试/开发人员的启示:
- Android 系统服务并不是“坚不可摧”、“密不透风”的,安全审计过程中如果发现框架层的系统服务存在漏洞,往往能够“大杀四方”;
- Android 系统应用具有特殊的高规格权限,能够拉起任意组件的能力如果被攻击者利用,那么后果是十分可怕的;
- PendingIntent 具有将发送方 appA 的权限的传递给接收方的能力,这直接决定了 PendingIntent 劫持类型的漏洞作用在系统应用上时,恶意应用将获得极大的权力。
最后,希望我也能早日挖到这类性质的漏洞吧哈哈…
以上是关于Android LaunchAnywhere组件权限绕过漏洞的主要内容,如果未能解决你的问题,请参考以下文章
Android LaunchAnywhere组件权限绕过漏洞
Android LaunchAnyWhere (Google Bug 7699048)漏洞具体解释及防御措施
Android BroadcastAnyWhere(Google Bug 17356824)漏洞具体分析