Android Content Provider Security
0x00 科普
內(nèi)容提供器用來存放和獲取數(shù)據(jù)并使這些數(shù)據(jù)可以被所有的應(yīng)用程序訪問。它們是應(yīng)用程序之間共享數(shù)據(jù)的唯一方法;不包括所有Android軟件包都能訪問的公共儲(chǔ)存區(qū)域。Android為常見數(shù)據(jù)類型(音頻,視頻,圖像,個(gè)人聯(lián)系人信息,等等)裝載了很多內(nèi)容提供器。你可以看到在android.provider包里列舉了一些。你還能查詢這些提供器包含了什么數(shù)據(jù)。當(dāng)然,對(duì)某些敏感內(nèi)容提供器,必須獲取對(duì)應(yīng)的權(quán)限來讀取這些數(shù)據(jù)。
如果你想公開你自己的數(shù)據(jù),你有兩個(gè)選擇:你可以創(chuàng)建你自己的內(nèi)容提供器(一個(gè)ContentProvider子類)或者你可以給已有的提供器添加數(shù)據(jù),前提是存在一個(gè)控制同樣類型數(shù)據(jù)的內(nèi)容提供器且你擁有讀寫權(quán)限。
0x01 知識(shí)要點(diǎn)
參考:http://developer.android.com/guide/topics/providers/content-providers.html
Content URIs
content URI 是一個(gè)標(biāo)志provider中的數(shù)據(jù)的URI.Content URI中包含了整個(gè)provider的以符號(hào)表示的名字(它的authority) 和指向一個(gè)表的名字(一個(gè)路徑).當(dāng)你調(diào)用一個(gè)客戶端的方法來操作一個(gè)provider中的一個(gè)表,指向表的content URI是參數(shù)之一.
A. 標(biāo)準(zhǔn)前綴表明這個(gè)數(shù)據(jù)被一個(gè)內(nèi)容提供器所控制。它不會(huì)被修改。
B. URI的權(quán)限部分;它標(biāo)識(shí)這個(gè)內(nèi)容提供器。對(duì)于第三方應(yīng)用程序,這應(yīng)該是一個(gè)全稱類名(小寫)以確保唯一性。權(quán)限在元素的權(quán)限屬性中進(jìn)行聲明:
<provider name=".TransportationProvider"authorities="com.example.transportationprovider". . . >C. 用來判斷請(qǐng)求數(shù)據(jù)類型的路徑。這可以是0或多個(gè)段長。如果內(nèi)容提供器只暴露了一種數(shù)據(jù)類型(比如,只有火車),這個(gè)分段可以沒有。如果提供器暴露若干類型,包括子類型,那它可以是多個(gè)分段長-例如,提供"land/bus", "land/train", "sea/ship", 和"sea/submarine"這4個(gè)可能的值。
D. 被請(qǐng)求的特定記錄的ID,如果有的話。這是被請(qǐng)求記錄的_ID數(shù)值。如果這個(gè)請(qǐng)求不局限于單個(gè)記錄, 這個(gè)分段和尾部的斜線會(huì)被忽略:
content://com.example.transportationprovider/trainsContentResolver
ContentResolver的方法們提供了對(duì)存儲(chǔ)數(shù)據(jù)的基本的"CRUD" (增刪改查)功能
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | getIContentProvider() ??????Returns the Binder object for this provider. delete(Uri uri, String selection, String[] selectionArgs) -----abstract ??????A request to delete one or more rows. insert(Uri uri, ContentValues values) ??????Implement this to insert a new row. query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) ??????Receives a query request from a client in a local process, and returns a Cursor. update(Uri uri, ContentValues values, String selection, String[] selectionArgs) ??????Update a content URI. openFile(Uri uri, String mode) ??????Open a file blob associated with a content URI. |
Sql注入
sql語句拼接
| 1 2 | // 通過連接用戶輸入到列名來構(gòu)造一個(gè)選擇條款 String mSelectionClause =? "var = " + mUserInput; |
參數(shù)化查詢
| 1 2 | // 構(gòu)造一個(gè)帶有占位符的選擇條款 String mSelectionClause =? "var = ?"; |
權(quán)限
下面的 元素請(qǐng)求對(duì)用戶詞典的讀權(quán)限:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">申請(qǐng)某些protectionLevel="dangerous"的權(quán)限
<uses-permission android:name="com.huawei.dbank.v7.provider.DBank.READ_DATABASE"/><permission android:name="com.huawei.dbank.v7.provider.DBank.READ_DATABASE" android:protectionLevel="dangerous"></permission>android:protectionLevel
normal:默認(rèn)值。低風(fēng)險(xiǎn)權(quán)限,只要申請(qǐng)了就可以使用,安裝時(shí)不需要用戶確認(rèn)。
dangerous:像WRITE_SETTING和SEND_SMS等權(quán)限是有風(fēng)險(xiǎn)的,因?yàn)檫@些權(quán)限能夠用來重新配置設(shè)備或者導(dǎo)致話費(fèi)。使用此protectionLevel來標(biāo)識(shí)用戶可能關(guān)注的一些權(quán)限。Android將會(huì)在安裝程序時(shí),警示用戶關(guān)于這些權(quán)限的需求,具體的行為可能依據(jù)Android版本或者所安裝的移動(dòng)設(shè)備而有所變化。
signature:這些權(quán)限僅授予那些和本程序應(yīng)用了相同密鑰來簽名的程序。
signatureOrSystem:與signature類似,除了一點(diǎn),系統(tǒng)中的程序也需要有資格來訪問。這樣允許定制Android系統(tǒng)應(yīng)用也能獲得權(quán)限,這種保護(hù)等級(jí)有助于集成系統(tǒng)編譯過程。
API
Contentprovider組件在API-17(android4.2)及以上版本由以前的exported屬性默認(rèn)ture改為默認(rèn)false。
Contentprovider無法在android2.2(API-8)申明為私有。
<!-- *** POINT 1 *** Do not (Cannot) implement Private Content Provider in Android 2.2 (API Level 8) or earlier. --> <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17" />關(guān)鍵方法
- public void addURI (String authority, String path, int code)
- public static String decode (String s)
- public ContentResolver getContentResolver()
- public static Uri parse(String uriString)
- public ParcelFileDescriptor openFile (Uri uri, String mode)
- public final Cursor query(Uri uri, String[] projection,String selection, String[] selectionArgs, String sortOrder)
- public final int update(Uri uri, ContentValues values, String where,String[] selectionArgs)
- public final int delete(Uri url, String where, String[] selectionArgs)
- public final Uri insert(Uri url, ContentValues values)
0x02 content provider 分類
這個(gè)老外分的特別細(xì),個(gè)人認(rèn)為就分private、public、in-house差不多夠用。
0x03 安全建議
0x04 測(cè)試方法
1、反編譯查看AndroidManifest.xml(drozer掃描)文件定位content provider是否導(dǎo)出,是否配置權(quán)限,確定authority
| 1 2 | drozer: run app.provider.info -a cn.etouch.ecalendar |
2、反編譯查找path,關(guān)鍵字addURI、hook api 動(dòng)態(tài)監(jiān)測(cè)推薦使用zjdroid
3、確定authority和path后根據(jù)業(yè)務(wù)編寫POC、使用drozer、使用小工具Content Provider Helper、adb shell // 沒有對(duì)應(yīng)權(quán)限會(huì)提示錯(cuò)誤
| 1 2 3 4 5 6 | adb shell: adb shell content query --uri <URI> [--user <USER_ID>] [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>] content query --uri content://settings/secure --projection name:value --where "name='new_setting'" --sort "name ASC" adb shell content insert --uri content://settings/secure --bind name:s:new_setting --bind value:s:new_value adb shell content update --uri content://settings/secure --bind value:s:newer_value --where "name='new_setting'" adb shell content delete --uri content://settings/secure --where "name='new_setting'" |
| 1 2 | drozer: run app.provider.query content://telephony/carriers/preferapn --vertical |
0x05 案例
案例1:直接暴露
- WooYun: 盛大Youni有你Android版敏感信息泄露(可讀用戶本地消息)
- WooYun: 新浪微博Android應(yīng)用本地信息泄露
- WooYun: 盛大起點(diǎn)讀書Android客戶端token等用戶敏感信息泄露
- WooYun: 傲游瀏覽器限制不嚴(yán)格可導(dǎo)致網(wǎng)頁欺詐攻擊
- WooYun: 搜狗手機(jī)瀏覽器隱私泄露和主頁篡改漏洞二合一(需要手機(jī)里有惡意應(yīng)用)
- WooYun: 酷派S6流量監(jiān)控繞過(偷跑流量不是事兒)
- WooYun: 酷派最安全手機(jī)s6通知欄管理權(quán)限繞過
案例2:需權(quán)限訪問
- WooYun: 米聊Android版敏感信息泄露(可讀用戶本地消息)
- WooYun: 華為網(wǎng)盤content provider組件可能泄漏用戶信息
- WooYun: 人人客戶端權(quán)限問題導(dǎo)致隱私泄露
案例3:openFile文件遍歷
- WooYun: 趕集網(wǎng)Android客戶端Content Provider組件任意文件讀取漏洞
- WooYun: 獵豹瀏覽器(Android版)任意私有文件數(shù)據(jù)可被本地第三方竊取漏洞
- WooYun: 58同城Android客戶端遠(yuǎn)程文件寫入漏洞
Override openFile method
錯(cuò)誤寫法1:
| 1 2 3 4 5 6 | private static String IMAGE_DIRECTORY = localFile.getAbsolutePath(); public ParcelFileDescriptor openFile(Uri paramUri, String paramString) ????throws FileNotFoundException { ??File file = new File(IMAGE_DIRECTORY, paramUri.getLastPathSegment()); ??return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); } |
錯(cuò)誤寫法2:URI.parse()
| 1 2 3 4 5 6 | private static String IMAGE_DIRECTORY = localFile.getAbsolutePath(); public ParcelFileDescriptor openFile(Uri paramUri, String paramString) ????throws FileNotFoundException { ????File file = new File(IMAGE_DIRECTORY, Uri.parse(paramUri.getLastPathSegment()).getLastPathSegment()); ????return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); } |
POC1:
| 1 2 3 4 5 6 7 | String target = "content://com.example.android.sdk.imageprovider/data/" + "..%2F..%2F..%2Fdata%2Fdata%2Fcom.example.android.app%2Fshared_prefs%2FExample.xml"; ContentResolver cr = this.getContentResolver(); FileInputStream fis = (FileInputStream)cr.openInputStream(Uri.parse(target)); byte[] buff = new byte[fis.available()]; in.read(buff); |
POC2:double encode
| 1 2 3 4 5 6 7 | String target = "content://com.example.android.sdk.imageprovider/data/" + "%252E%252E%252F%252E%252E%252F%252E%252E%252Fdata%252Fdata%252Fcom.example.android.app%252Fshared_prefs%252FExample.xml"; ContentResolver cr = this.getContentResolver(); FileInputStream fis = (FileInputStream)cr.openInputStream(Uri.parse(target)); byte[] buff = new byte[fis.available()]; in.read(buff); |
解決方法Uri.decode()
| 1 2 3 4 5 6 7 8 9 10 | private static String IMAGE_DIRECTORY = localFile.getAbsolutePath(); ??public ParcelFileDescriptor openFile(Uri paramUri, String paramString) ??????throws FileNotFoundException { ????String decodedUriString = Uri.decode(paramUri.toString()); ????File file = new File(IMAGE_DIRECTORY, Uri.parse(decodedUriString).getLastPathSegment()); ????if (file.getCanonicalPath().indexOf(localFile.getCanonicalPath()) != 0) { ??????throw new IllegalArgumentException(); ????} ????return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); ??} |
0x06 參考
https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=111509535
http://www.jssec.org/dl/android_securecoding_en.pdf
http://developer.android.com/intl/zh-cn/reference/android/content/ContentProvider.html
0x07 相關(guān)閱讀
http://zone.wooyun.org/content/15097
http://drops.wooyun.org/tips/2997
原文地址: http://drops.wooyun.org/tips/4314
總結(jié)
以上是生活随笔為你收集整理的Android Content Provider Security的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Activtity Se
- 下一篇: Android Service Secu