# Android安全开发之Provider组件安全

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2016-06-01T09:15:00Z
Canonical: https://wepostx.com/topics/288

> 乌云历史资料归档
>
> **原始作者：** 阿里移动安全
> **原始编号：** superkieran-wooyundrops:1231
> **原始发布时间：** 2016-06-01 17:15
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

作者：[email protected]

# 0x00 Content Provider组件简介

Content Provider组件是Android应用的重要组件之一，管理对数据的访问，主要用于不同的应用程序之间实现数据共享的功能。Content Provider的数据源不止包括SQLite数据库，还可以是文件数据。通过将数据储存层和应用层分离，Content Provider为各种数据源提供了一个通用的接口。

![原文图片](/media/2016/06/0487504cfdd723b47f2b8eda6d215625)

创建一个自己的Content Provider需要继承自ContentProvider抽象类，需要重写其中的onCreate()、query()、insert()、update()、delete()、getType()六个抽象方法，这些方法实现对底层数据源的增删改查等操作。还需在AndroidManifest文件注册Content Provider，注册时指定访问权限、exported属性、authority属性值等。

![原文图片](/media/2016/06/80ef3f6121d8d0626f81186268870bd3)

其它APP使用ContentResolver对象来查询和操作Content Provider，此对象具有Content Provider中同名的方法名。这样其他APP接就可以访问Content Provider对应的数据源的底层数据，而无须知道数据的结构或实现。
如何定位到具体的数据？
采用Content Uri，一个Content Uri如下所示：

```text
content://com.jaq.providertest.friendsprovider/friends
```
它的组成一般分为三部分：

- content://:作为 content Uri的特殊标识(必须);

- 权(authority):用于唯一标识这个Content Provider，外部访问者可以根据这个标识找到它；在AndroidManifest中也配置的有；

- 路径(path): 所需要访问数据的路径，根据业务而定。

这些内容就不具体展开详谈了，详见参考【1】【4】。

# 0x01 风险简介

如果在AndroidManifest文件中将某个Content Provider的exported属性设置为true，则多了一个攻击该APP的攻击点。如果此Content Provider的实现有问题，则可能产生任意数据访问、SQL注入、目录遍历等风险。

## 1.1 私有权限定义错误导致数据被任意访问
私有权限定义经常发生的风险是：定义了私有权限，但是却根本没有定义私有权限的级别，或者定义的权限级别不够，导致恶意应用只要声明这个权限就能够访问到相应的Content Provider提供的数据，造成数据泄露。
以公开的乌云漏洞WooYun-2014-57590为例：
某网盘客户端使用了自己的私有权限，但是在AndroidManifest中却没有定义私有权限，其它APP只要声明这个权限就能访问此网盘客户端提供的Provider，从而访问到用户数据。
在网盘客户端的AndroidManifest中注册Provider时，声明了访问时需要的读写权限，并且权限为客户端自定义的私有权限：

![原文图片](/media/2016/06/42bada05ae84e9ce0511d126549b6396)

但是在AndroidManifest中却没有见到私有权限“com.huawei.dbank.v7.provider.DBank.READ_DATABASE”和“com.huawei.dbank.v7.provider.DBank.WRITE_DATABASE”的定义：

![原文图片](/media/2016/06/d67c3c53ba53d633a09e49b290cdd0f9)

反编译客户端后查看到的URI，根据这些可以构造访问到Provider的URI：

![原文图片](/media/2016/06/f144164788d026304fefe46ef966f072)

编写POC
以查看网盘下载的文件列表为例，
在POC的AndroidManifest中声明私有权限，权限的保护级别定义为最低级“normal”：

![原文图片](/media/2016/06/0e6d1fe21a98ae31bb5c3394bd359e4c)

主要代码为：

![原文图片](/media/2016/06/22ca10ef71ae14da2cd4a9eb3e537b14)

拿到数据库中保存的下载列表数据：

![原文图片](/media/2016/06/e32749515288f9fd89066c54deda57a6)

对应的数据库：

![原文图片](/media/2016/06/66c4b699ab6b6332d46af23c1028f155)

这样任意的恶意应用程序就可以访问到用户网盘的上传、下载记录，网盘里面存的文件列表等隐私信息。
再以公开的乌云漏洞wooyun-2013-039697为例：
定义了私有权限，但是保护等级设置成为了dangerous或者normal，这样的保护等级对于一些应用的Provide重要性相比保护级低了。
Provider为：

![原文图片](/media/2016/06/217c88c53e66ed57db60f82089397be0)

私有权限“com.renren.mobile.android.permission.PERMISSION_ADD_ACCOUNT”的定义为：

![原文图片](/media/2016/06/8e297caee15594e22fa6d234cb65e37c)

反编译客户端，看到AcountProvider对应的实现：

![原文图片](/media/2016/06/2968777f5b80c3c6a3b098228b0e15ba)

编写POC：
在AndroidManifest中定义和声明权限：

![原文图片](/media/2016/06/7302d51ddb7c2bfe689c50f72a90fe07)

主要代码为：

![原文图片](/media/2016/06/3877f7ffe9bd74342a31718a3eccde44)

可看到用户的账户信息，包括uid，手机号，加密后的密码等：

![原文图片](/media/2016/06/9d940693d00744ca1b2205409e43d2af)

## 1.2 本地SQL注入
当Content Provider的数据源是SQLite数据库时，如果实现不当，而Provider又是暴露的话，则可能会引发本地SQL注入漏洞。
Content Provider的query( )的方法定义为：

![原文图片](/media/2016/06/0c14fe5cee74848b12d39724473a83a2)

其中参数：

- uri: 为content Uri，要查询的数据库；

- projection：为要查询的列名；

- selection和selectionArgs：要指定查询条件；

- sortOrder：查询结果如何排序。

query() 与 SQL 查询对比如下：

![原文图片](/media/2016/06/709f1d42ed69ed56d26f65a785b4710a)

如果query( )中使用的是拼接字符串组成SQL语句的形式去查询底层的SQLite数据库时，容易发生SQL注入。
以乌云公开漏洞wooyun-2016-0175294为例：
客户端的com.sohu.sohuvideo.provider.PlayHistoryProvider的exported属性为“true”：

![原文图片](/media/2016/06/22661c089762b027266a78330e854eee)

反编译客户端，追踪PlayHistoryProvider的实现，发现是用拼接字符串形式构造原始的SQL查询语句：

![原文图片](/media/2016/06/3f86fa57e47f8f9b9b20d1c5cc761877)

使用drozer工具，证明漏洞：

![原文图片](/media/2016/06/2c39ae6874a0459ea36da33c66fe01d6)

对外暴露的Content Provider实现了openFile()接口，因此其他有相应调用该Content Provider权限的应用即可调用Content Provider的openFile()接口进行文件数据访问。但是如果没有进行Content Provider访问权限控制和对访问的目标文件的Uri进行有效判断，攻击者利用文件目录遍历可访问任意可读文件，更有甚者可以往手机设备可写目录中写入任意数据。
例子1
以乌云公开漏洞wooyun-2013-044407为例：
此APP实现中定义了一个可以访问本地文件的Content Provider组件，为com.ganji.android.jobs.html5.LocalFileContentProvider，因为使用了minSdkServison=“8”，targetSdkVersion=”13”，即此Content Provider采用默认的导出配置，即android:exported=”true”：

![原文图片](/media/2016/06/382b317dd05a5655931c3f35941e834d)

该Provider实现了openFile()接口：

![原文图片](/media/2016/06/4058f07d2c209b7cea22a1c868690600)

通过此接口可以访问内部存储app_webview目录下的数据，由于后台未能对目标文件地址进行有效判断，可以通过”../”实现目录遍历，实现对任意私有数据的访问。
例子2
某社交应用客户端，使用了的minSDKVersion为8，定义了私有权限，并且android:protectionLevel设为了signature

![原文图片](/media/2016/06/984768248be76c2a48bb159350d547aa)

有一个对外暴露的Content Provider，即com.facebook.lite.photo.MediaContentProvider，此Provider没有设置访问权限，而另外一个Provider是设置了访问权限的：

![原文图片](/media/2016/06/34c171ff901a981ae41da2322fb6afc4)

在MediaContentProvider中实现了openFile()接口，没有对传入的URI进行限制和过滤：

![原文图片](/media/2016/06/a69e8be9a15466862257d5d3dc779698)

此接口本来只想让用户访问照片信息的，但是却可以突破限制，读取其他文件：
POC：

![原文图片](/media/2016/06/f659e75e519445ed4fb3f4f648bc3cdc)

读取到其他文件的内容为：

![原文图片](/media/2016/06/5044111f80c770d828803f3e91ccb371)

另外看到Openfile()接口的实现中，如果要访问的文件不存在，就会创建此文件，还有可能的风险就是在应用的目录中写入任意文件。

# 0x02 阿里聚安全开发者建议

在进行APP设计时，要清楚哪些Provider的数据是用户隐私数据或者其他重要数据，考虑是否要提供给外部应用使用，如果不需要提供，则在AndroidManifes文件中将其exported属性显式的设为“false”，这样就会减少了很大一部分的攻击面。
人工排查肯定比较麻烦，建议开发者使用阿里聚安全提供的安全扫描服务，在APP上线前进行自动化的安全扫描，尽早发现并规避这样的风险。
注意：
由于Android组件Content Provider无法在Android 2.2（即API Level 8）系统上设为不导出，因此建议声明最低SDK版本为8以上版本（这已经是好几年前的SDK了，现在一般都会大于此版本）；
由于API level 在17以下的所有应用的“android:exported”属性默认值都为true，因此如果应用的Content Provider不必要导出，建议显式设置注册的Content Provider组件的“android:exported”属性为false；
如果必须要有数据提供给外部应用使用，则做好设计，做好权限控制，明确什么样的外部应用可以使用，如对于本公司的应用在权限定义时用相同签名即可，合作方的应用检查其签名；不过还是尽量不提供用户隐私敏感信息。
对于必须暴露的Provider，如第二部分遇到的风险解决办法如下：

## 2.1 正确的定义私有权限
在AndroidManifest中定义私有权限的语法为：

![原文图片](/media/2016/06/19bb40b52f66c736a99f685b78d62b86)

其中android:protectionLevel的可选值分别表示：

- normal：默认值，低风险权限，在安装的时候，系统会自动授予权限给 application。

- dangerous：高风险权限，如发短信，打电话，读写通讯录。使用此protectionLevel来标识用户可能关注的一些权限。Android将会在安装程序时，警示用户关于这些权限的需求，具体的行为可能依据Android版本或者所安装的移动设备而有所变化。

- signature： 签名权限，在其他 app 引用声明的权限的时候，需要保证两个 app 的签名一致。这样系统就会自动授予权限给第三方 app，而不提示给用户。

- signatureOrSystem：除了具有相同签名的APP可以访问外，Android系统中的程序有权限访问。

大部分开放的Provider，是提供给本公司的其他应用使用的，一般的话一个公司打包签名APP的签名证书都应该是一致的，这种情况下，Provider的android:protectionLevel应为设为“signature”。

## 2.2 防止本地SQL注入
注意：一定不要使用拼接来组装SQL语句。
如果Content Provider的数据源是SQLite数据库，如果使用拼接字符串的形式组成原始SQL语句执行，则会导致SQL注入。
如下的选择子句：

![原文图片](/media/2016/06/8a06e83bdd2b8d5050d2f54b56080274)

如果执行此操作，则会允许用户将恶意 SQL 串连到 SQL 语句上。
例如，用户可以为 mUserInput 输入“nothing; DROP TABLE ** ;”，这会生成选择子句

```text
#!sql var = nothing; DROP TABLE **;
```
由于选择子句是作为SQL语句处理，因此这可能会导致提供程序擦除基础 SQLite 数据库中的所有表（除非提供程序设置为可捕获 SQL 注入尝试）。
使用参数化查询：
要避免此问题，可使用一个“ ? ” 作为可替换参数的选择子句以及一个单独的选择参数数组。
执行此操作时，用户输入直接受查询约束，而不解释为 SQL 语句的一部分。
由于用户输入未作为 SQL 处理，因此无法注入恶意 SQL。
请使用此选择子句，而不要使用串连来包括用户输入：

```text
#!java String mSelectionClause = “var = ?”;
```
按如下所示设置选择参数数组：

```text
#!java String[] selectionArgs = {“”};
```
按如下所示将值置于选择参数数组中：

```text
#!java selectionArgs[0] = mUserInput;
```
还可调用SQLiteDatabase类中的参数化查询query()方法：

![原文图片](/media/2016/06/23f88d3692478c0d090b3c3ab8d58edd)

## 3.3 防止目录遍历
1、去除Content Provider中没有必要的openFile()接口。
2、过滤限制跨域访问，对访问的目标文件的路径进行有效判断：
使用Uri.decode()先对Content Query Uri进行解码后，再过滤如可通过“../”实现任意可读文件的访问的Uri字符串，如：

![原文图片](/media/2016/06/21df686c1eb1f655fd6a961f04ab0e51)

## 2.4 通过检测签名来授权合作方应用访问
如果必须给合作方的APP提供Provider的访问权限，而合作方的APP签名证书又于自己公司的不同，可将合作方的APP的签名哈希值预埋在提供Provider的APP中，提供Provider的APP要检查请求访问此Provider的APP的签名，签名匹配通过才让访问。

# 0x03 参考

【1】《内容提供程序基础知识》[https://developer.android.com/guide/topics/providers/content-provider-basics.html](https://developer.android.com/guide/topics/providers/content-provider-basics.html)
【2】《Android app端的sql注入》[http://zone.wooyun.org/content/15097](http://zone.wooyun.org/content/15097)
【3】《Android - Content Providers》 [http://www.tutorialspoint.com/android/android_content_providers.htm](http://www.tutorialspoint.com/android/android_content_providers.htm)
【4】 [http://www.compiletimeerror.com/2013/12/content-provider-in-android.html](http://www.compiletimeerror.com/2013/12/content-provider-in-android.html)
【5】 [https://developer.android.com/guide/topics/manifest/permission-element.html?hl=zh-cn](https://developer.android.com/guide/topics/manifest/permission-element.html?hl=zh-cn)
【6】 [https://developer.android.com/guide/topics/manifest/permission-element.html](https://developer.android.com/guide/topics/manifest/permission-element.html)
【7】  [WooYun: 人人客户端权限问题导致隐私泄露](http://www.wooyun.org/bugs/wooyun-2013-039697)
【8】  [WooYun: 华为网盘content provider组件可能泄漏用户信息](http://www.wooyun.org/bugs/wooyun-2014-057590)
【9】 《Android Content Provider Security》[http://drops.wooyun.org/tips/4314](http://drops.wooyun.org/tips/4314)
【10】  [WooYun: 搜狐app注入泄露观看历史](http://www.wooyun.org/bugs/wooyun-2016-0175294)
【11】《Android Content Provider Security》[http://drops.wooyun.org/tips/4314](http://drops.wooyun.org/tips/4314)
【12】  [WooYun: 赶集网Android客户端Content Provider组件任意文件读取漏洞](http://www.wooyun.org/bugs/wooyun-2013-044407)
【13】  [WooYun: 58同城Android客户端远程文件写入漏洞](http://www.wooyun.org/bugs/wooyun-2013-044411)
【14】 《Content Provider文件目录遍历漏洞浅析》，[https://jaq.alibaba.com/blog.htm?id=61](https://jaq.alibaba.com/blog.htm?id=61)
【15】 [https://github.com/programa-stic/security-advisories/tree/master/FacebookLite](https://github.com/programa-stic/security-advisories/tree/master/FacebookLite)

## Replies
