Android Content Provider Security
0x00 科普
內容提供器用來存放和獲取數據并使這些數據可以被所有的應用程序訪問。它們是應用程序之間共享數據的唯一方法;不包括所有Android軟件包都能訪問的公共儲存區域。Android為常見數據類型(音頻,視頻,圖像,個人聯系人信息,等等)裝載了很多內容提供器。你可以看到在android.provider包里列舉了一些。你還能查詢這些提供器包含了什么數據。當然,對某些敏感內容提供器,必須獲取對應的權限來讀取這些數據。
如果你想公開你自己的數據,你有兩個選擇:你可以創建你自己的內容提供器(一個ContentProvider子類)或者你可以給已有的提供器添加數據,前提是存在一個控制同樣類型數據的內容提供器且你擁有讀寫權限。
0x01 知識要點
參考:http://developer.android.com/guide/topics/providers/content-providers.html
Content URIs
content URI 是一個標志provider中的數據的URI.Content URI中包含了整個provider的以符號表示的名字(它的authority) 和指向一個表的名字(一個路徑).當你調用一個客戶端的方法來操作一個provider中的一個表,指向表的content URI是參數之一.
A. 標準前綴表明這個數據被一個內容提供器所控制。它不會被修改。
B. URI的權限部分;它標識這個內容提供器。對于第三方應用程序,這應該是一個全稱類名(小寫)以確保唯一性。權限在元素的權限屬性中進行聲明:
<provider name=".TransportationProvider"authorities="com.example.transportationprovider". . . >C. 用來判斷請求數據類型的路徑。這可以是0或多個段長。如果內容提供器只暴露了一種數據類型(比如,只有火車),這個分段可以沒有。如果提供器暴露若干類型,包括子類型,那它可以是多個分段長-例如,提供"land/bus", "land/train", "sea/ship", 和"sea/submarine"這4個可能的值。
D. 被請求的特定記錄的ID,如果有的話。這是被請求記錄的_ID數值。如果這個請求不局限于單個記錄, 這個分段和尾部的斜線會被忽略:
content://com.example.transportationprovider/trainsContentResolver
ContentResolver的方法們提供了對存儲數據的基本的"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 | // 通過連接用戶輸入到列名來構造一個選擇條款 String mSelectionClause =? "var = " + mUserInput; |
參數化查詢
| 1 2 | // 構造一個帶有占位符的選擇條款 String mSelectionClause =? "var = ?"; |
權限
下面的 元素請求對用戶詞典的讀權限:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">申請某些protectionLevel="dangerous"的權限
<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:默認值。低風險權限,只要申請了就可以使用,安裝時不需要用戶確認。
dangerous:像WRITE_SETTING和SEND_SMS等權限是有風險的,因為這些權限能夠用來重新配置設備或者導致話費。使用此protectionLevel來標識用戶可能關注的一些權限。Android將會在安裝程序時,警示用戶關于這些權限的需求,具體的行為可能依據Android版本或者所安裝的移動設備而有所變化。
signature:這些權限僅授予那些和本程序應用了相同密鑰來簽名的程序。
signatureOrSystem:與signature類似,除了一點,系統中的程序也需要有資格來訪問。這樣允許定制Android系統應用也能獲得權限,這種保護等級有助于集成系統編譯過程。
API
Contentprovider組件在API-17(android4.2)及以上版本由以前的exported屬性默認ture改為默認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" />關鍵方法
- 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 分類
這個老外分的特別細,個人認為就分private、public、in-house差不多夠用。
0x03 安全建議
0x04 測試方法
1、反編譯查看AndroidManifest.xml(drozer掃描)文件定位content provider是否導出,是否配置權限,確定authority
| 1 2 | drozer: run app.provider.info -a cn.etouch.ecalendar |
2、反編譯查找path,關鍵字addURI、hook api 動態監測推薦使用zjdroid
3、確定authority和path后根據業務編寫POC、使用drozer、使用小工具Content Provider Helper、adb shell // 沒有對應權限會提示錯誤
| 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應用本地信息泄露
- WooYun: 盛大起點讀書Android客戶端token等用戶敏感信息泄露
- WooYun: 傲游瀏覽器限制不嚴格可導致網頁欺詐攻擊
- WooYun: 搜狗手機瀏覽器隱私泄露和主頁篡改漏洞二合一(需要手機里有惡意應用)
- WooYun: 酷派S6流量監控繞過(偷跑流量不是事兒)
- WooYun: 酷派最安全手機s6通知欄管理權限繞過
案例2:需權限訪問
- WooYun: 米聊Android版敏感信息泄露(可讀用戶本地消息)
- WooYun: 華為網盤content provider組件可能泄漏用戶信息
- WooYun: 人人客戶端權限問題導致隱私泄露
案例3:openFile文件遍歷
- WooYun: 趕集網Android客戶端Content Provider組件任意文件讀取漏洞
- WooYun: 獵豹瀏覽器(Android版)任意私有文件數據可被本地第三方竊取漏洞
- WooYun: 58同城Android客戶端遠程文件寫入漏洞
Override openFile method
錯誤寫法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); } |
錯誤寫法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 相關閱讀
http://zone.wooyun.org/content/15097
http://drops.wooyun.org/tips/2997
原文地址: http://drops.wooyun.org/tips/4314
總結
以上是生活随笔為你收集整理的Android Content Provider Security的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Activtity Se
- 下一篇: Android Service Secu