谷歌近期对外公布了2016年10月的Nexus Security Bulletin ,这其中包含一个由360手机卫士阿尔法团队(Alpha Team)提交的电子邮件信息泄露漏洞(CVE-2016-3918 ),谷歌对此漏洞的评级为高危险等级。该漏洞可导致恶意应用获取到电子邮件内的数据,可能是电子邮件内容、电子邮件附件甚至账号密码。目前谷歌已经修复该漏洞并向OEM厂商推送了补丁,本文将对此漏洞进行分析。
本文的测试环境和代码版本如下:
- SDK Version: 23,
- Android 6.0.1 Build: MOB30Y
- Branch: android-6.0.1_r60
漏洞成因
在Android AOSP的Email应用程序的源码中,我们可以看到在AndroidManifest.xml 文件中存在名为AttachmentProvider的ContentProvider。
- <provider
- android:name=".provider.AttachmentProvider"
- android:authorities="com.android.email.attachmentprovider"
- android:grantUriPermissions="true"
- android:exported="true"
- android:readPermission="com.android.email.permission.READ_ATTACHMENT"
- />
其主要属性如下:
- exported true即对外开放
- authorities com.android.email.attachmentprovider 即URI唯一标识
- readPermission com.android.email.permission.READ_ATTACHMENT 即读取需要此权限
通过查询我们可以了解到com.android.email.permission.READ_ATTACHMENT权限的protectionLevel为 dangerous,即可被第三方应用获取到。
- <permission
- android:name="com.android.email.permission.READ_ATTACHMENT"
- android:permissionGroup="android.permission-group.MESSAGES"
- android:protectionLevel="dangerous"
- android:label="@string/permission_read_attachment_label"
- android:description="@string/permission_read_attachment_desc"/>
在确定此ContentProvider可以被第三方应用接触到之后,我们定位AttachmentProvider的源码。 源码路径如下:
- /packages/apps/Email/provider_src/com/android/email/provider/AttachmentProvider.java
通过阅读源码我们可以发现AttachmentProvider中实现了一个public的openFile 接口,该接口会返回一个ParcelFileDescriptor类型的对象供调用者打开文件。
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
进入openFile接口立刻就会判断mode值是否为 "w" ,如果为w则会返回一个可写的文件描述符。但在返回之前,函数的实现代码进行了权限检查,如果调用者没有com.android.email.permission.ACCESS_PROVIDER 权限的话是会抛异常的。而该权限的声明如下:
- <permission
- android:name="com.android.email.permission.ACCESS_PROVIDER"
- android:protectionLevel="signature"
- android:label="@string/permission_access_provider_label"
- android:description="@string/permission_access_provider_desc"/>
可以看到该权限是无法被第三方应用获取到的,所以获取可写的权限是不可行的。 继续往下分析代码。
- List<String> segments = uri.getPathSegments();
- String accountId = segments.get(0);
- String id = segments.get(1);
- String format = segments.get(2);
- if (AttachmentUtilities.FORMAT_THUMBNAIL.equals(format)) {
- int width = Integer.parseInt(segments.get(3));
- int height = Integer.parseInt(segments.get(4));
- ...
- }
- else {
- return ParcelFileDescriptor.open(
- new File(getContext().getDatabasePath(accountId + ".db_att"), id),
- ParcelFileDescriptor.MODE_READ_ONLY);
- }
接下来一系列代码会从uri.getPathSegments()中分割开不同的字段,并从中读取相应的配置参数。 当 format参数不等于"THUMBNAIL"时,该代码将会直接返回一个getDatabasePath()该目录下名为id 的文件的文件描述符。 而id参数是上面从uri.getPathSegments().get(1) 得到的,获取之后则没有任何处理。 uri.getPathSegments()方法的作用是将字符串以"/"进行分割,但由于其没有对经过url 编码的字符串进行解码,导致在处理过程中,对于/编码之后的%2f则将不做处理,从而绕过getPathSegments的分割。
漏洞利用
根据上面我们对于代码的分析,可以得出AttachmentProvider的uri如下:
- content://accountId/id/format/width/height
我们如果想利用此漏洞读取数据,为使程序流能够成功运行至目标位置,需要构造如下uri
- content://com.android.email.attachmentprovider/1/file_position/1/1/1
而且,getDatabasePath()的目录是/data/user/0/com.android.email/databases/,如果我们要读取Email 邮件的数据,则需要跳转至目标目录/data/data/com.android.email/来读取Email应用的sqlite数据库文件,我们需要构造如下 uri:
- content://com.android.email.attachmentprovider/1/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.android.email%2Fdatabases%2FEmailProvider.db/1/1/1
而EmailProvider.db中存储了我们已登陆账户的账户名和密码,对应的结构为HostAuth表的login和password 字段。 我们构造的PoC截图如下:
总结
至此,CVE-2016-3918漏洞的分析和利用已经完成。 经过我们的测试,目前多款主流手机机型的自带电子邮件客户端都存在该问题,如小米 NOTE、华为P9、三星S5等机型,为了账户的安全,请使用存在漏洞手机的用户暂停使用电子邮件客户端并清除账户信息,换至其他邮件客户端。