<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/4314

            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/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" />
            

            關鍵方法

            0x02 content provider 分類


            這個老外分的特別細,個人認為就分private、public、in-house差不多夠用。

            0x03 安全建議


            1. minSdkVersion不低于9
            2. 不向外部app提供的數據的私有content provider設置exported=“false”避免組件暴露(編譯api小于17時更應注意此點)
            3. 使用參數化查詢避免注入
            4. 內部app通過content provid交換數據設置protectionLevel=“signature”驗證簽名
            5. 公開的content provider確保不存儲敏感數據
            6. Uri.decode() before use ContentProvider.openFile()
            7. 提供asset文件時注意權限保護

            0x04 測試方法


            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
            

            0x05 案例


            案例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);
              }
            

            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

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线