日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android应用安全之Content Provider安全

發(fā)布時間:2025/3/15 Android 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android应用安全之Content Provider安全 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

android平臺提供了Content Provider,將一個應(yīng)用程序的指定數(shù)據(jù)集提供給其它應(yīng)用程序。這些數(shù)據(jù)可以存儲在文件系統(tǒng)、SQLite數(shù)據(jù)庫中,或以任何其它合理的方式存儲。其他應(yīng)用可以通過ContentResolver類從該內(nèi)容提供者中獲取或存入數(shù)據(jù)。

Content Provider通過URI(統(tǒng)一資源定位符)來訪問數(shù)據(jù),URI可以理解為訪問數(shù)據(jù)的唯一地址。

限制app對敏感Content Provider的訪問

Content Provider類提供了一種機(jī)制用來管理以及與其它應(yīng)用程序共享數(shù)據(jù)。在與其它應(yīng)用程序共享Content Provider的數(shù)據(jù)時,應(yīng)該實現(xiàn)訪問控制,禁止對敏感數(shù)據(jù)未經(jīng)授權(quán)的訪問。

有三種方法來限制對內(nèi)容提供者的訪問:

  • 公開的
  • 私有的
  • 內(nèi)部的

公開的

通過在AndroidManifest.xml文件中指定android:exported屬性為true,可以設(shè)置將Content Provider公開給其它應(yīng)用程序。對于API Level低于17的Android應(yīng)用程序,Content Provider默認(rèn)是public的,除非顯式指定android:exported="false"。例如:

<provider android:exported="true" android:name="MyContentProvider" android:authorities="com.example.mycontentprovider" />

如果一個Content Provider為公開的,Content Provider中存儲的數(shù)據(jù)就可以被其它應(yīng)用程序訪問。因此,它只能用于處理非敏感信息。

私有的

通過在AndroidManifest. xml文件中指定android:exported屬性為true,可以設(shè)置將Content Provider設(shè)置為私有的。從API Level17及以后,Content Provider默認(rèn)是私有的,除非顯式指定android:exported="true"。例如:

<provider android:exported="false" android:name="MyContentProvider" android:authorities="com.example.mycontentprovider" />

開發(fā)建議

  • 如果不需要與其他應(yīng)用程序進(jìn)行數(shù)據(jù)共享,就應(yīng)該在manifest文件中設(shè)置android:exported="false"。
  • 但是值得注意的是,在API Level低于8時,即使顯式地聲明了android:exported="false",其它應(yīng)用程序仍然可以訪問你的Content Provider。
  • 需要向外部提供數(shù)據(jù)的content provider則需設(shè)置訪問權(quán)限,如:
  •   下面的元素請求對用戶詞典的讀權(quán)限:

      <uses-permission android:name="android.permission.READ_USER_DICTIONARY">

      申請某些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>

    防止SQL注入

    ?數(shù)據(jù)查詢

    傳遞給ContentProvider的參數(shù)應(yīng)該被視為不可信的,不能直接用于sql查詢。?

    一個程序要訪問暴露的provider,首先要知道訪問的目標(biāo)地址,類似http協(xié)議,provider也有自己的規(guī)范,即類似content://com.aaaa.bbb.class/tablename

    其中,com.aaaa.bbb為包名,class為類名,tablename為表名,一般是這樣子,具體看自己定義了。

    看一個查詢例子:

    Cursor cursor = contentResolver.query(Words_CONTENT_URI, new String[]{"user","pwd"},null, null, null);

    這是調(diào)用contentResolver的query方法進(jìn)行數(shù)據(jù)庫查詢,返回一個cursor對象,即類似DataReader的東西,里面是返回結(jié)果。

    來看看query的參數(shù)

    Cursor android.content.ContentResolver.query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

    uri即content://com.aaaa.bbb.class/tablename

    projection即你要查詢的列名

    selectionselectionArgs共同控制后面的sql語句中的where內(nèi)容.

    sortOrder即order by xxx的內(nèi)容。

    那么例子中的查詢整個構(gòu)造的語句即:

    select user,pwd from tablename;

    2.Sql注入問題

    綜合上面的內(nèi)容,我們可以看到,query里至少兩個參數(shù)我們可控,一個是projection,一個是selection,這兩個都會影響SQL與的組成,也就為注入提供了可能。

    這里以某app為例,該app對外暴露了一個content provider,uri為:content://com.masaike.mobile.downloads/my_downloads,其中com.masaike.mobile為包名,downloads為庫的名字,my_downloads為表名(不一定,可自定義的哦)。

    現(xiàn)在語句這么寫:

    Cursor cursor = contentResolver.query("content://com.masaike.mobile.downloads/my_downloads", new String[]{"_id'","method"},null, null, null);

    其中_id和method為兩個字段名,我們在_id后面加個單引號,運行下看logcat內(nèi)容:

    ?

    從上圖很容易看出來,SQL語句因為有個單引號,導(dǎo)致出錯。所以注入是存在的。

    而如果我們修改projection的內(nèi)容為"* from sqlite_master where type='table';--",這樣子即在閉合后面查詢的情況下顯示出來全部的表名。當(dāng)然也可以構(gòu)造其他語句了。

    (參考cnrstar?http://lcx.cc/?i=4462)

    開發(fā)建議

    1.傳遞給ContentProvider的參數(shù)應(yīng)該被視為不可信的輸入,不應(yīng)該在沒有經(jīng)過任何處理的情況下直接用于SQL查詢。如果查詢語句中包含SQL代碼則可以返回數(shù)據(jù)或者允許攻擊者未授權(quán)地訪問應(yīng)用數(shù)據(jù)庫的數(shù)據(jù)。

    2.使用ContentProvider提供外部應(yīng)用程序進(jìn)行數(shù)據(jù)庫存取時應(yīng)使用帶占位符的參數(shù)化查詢防SQL注入。

    3.SQLiteDatabase對象的部分內(nèi)置方法是可以有效防SQL注入的,比如query(),insert(),update和delete(),另外,正確地使用rawQuery()也可以防止SQL注入,而execSQL()方法建議避免使用。

    (1)、使用SQLiteDatabase對象自帶的防SQL注入的方法,比如query(),insert(),update和delete():

    DatabaseHelper dbhelper = new DatabaseHelper(SqliteActivity.this,"sqliteDB");SQLiteDatabase db = dbhelper.getReadableDatabase();

    /*查詢操作,userInputID和userInputName是用戶可控制的輸入數(shù)據(jù) */

    Cursor cur = db.query("user", new String[]{"id","name"}, "id=? and name=?", new String[]{userInputID,userInputName}, null, null, null);

    /* 插入記錄操作*/

    ContentValues val = new ContentValues();val.put("id", userInputID);val.put("name", userInputName);db.insert("user", null, val);

    /*更新記錄操作*/

    ContentValues val = new ContentValues();val.put("id", userInputName);db.update("user", val, "id=?", new String[]{userInputID });

    /*刪除記錄操作*/

    db.delete("user", "id=? and name=?", new String[]{userInputID , userInputName });

    (2)、正確地使用SQLiteDatabase對象的rawQuery()方法(僅以查詢?yōu)槔?#xff09;:

    DatabaseHelper dbhelper = new DatabaseHelper(SqliteActivity.this,"sqliteDB");SQLiteDatabase db = dbhelper.getReadableDatabase();

    /* userInputID和userInputName是用戶可控制的輸入數(shù)據(jù)*/

    String[] selectionArgs = new String[]{userInputID , userInputName };String sql = "select * from user where id=? and name=?";//正確!此處綁定變量Cursor curALL = db.rawQuery(sql, selectionArgs);

    (3)、以下為錯誤案例!僅供參考:

    DatabaseHelper dbhelper = new DatabaseHelper(SqliteActivity.this,"sqliteDB");SQLiteDatabase db = dbhelper.getReadableDatabase();

    /*案例1:錯誤地使用rawQuery(),未綁定參數(shù)*/

    String sql = "select * from user where id=‘" + userInputID +"‘";//錯誤!動態(tài)拼接,未綁定變量Cursor curALL = db.rawQuery(sql, null);

    /*案例2:使用execSQL()方法*/

    String sql = "INSERT INTO user values(‘"+userInputID +"‘,‘"+userInputName +"‘)";//錯誤同上db.execSQL(sql);

    4.ContentProvider支持對指定的Uri分別設(shè)置讀權(quán)限和寫權(quán)限,建議只開放能完成任務(wù)的最小權(quán)限。

    (參考http://shikezhi.com/html/2015/android_0819/134898.html)

    規(guī)范ContentProvider的url

    沒有正確的覆寫openFile方法,導(dǎo)致攻擊者可以通過更改訪問目錄,遍歷系統(tǒng)中所有文件。

    通過使用ContentProvider.openFile()方法,可以方便其它應(yīng)用訪問你的應(yīng)用程序數(shù)據(jù)。根據(jù)ContentProvider的實現(xiàn)方式,使用openFile方法可以導(dǎo)致目錄遍歷漏洞。因此,當(dāng)通過ContentProvider交換文件的時候,文件路徑在使用之前應(yīng)該被規(guī)范化。

    不合規(guī)代碼Example 1

    在這個不合規(guī)代碼示例中,試圖通過調(diào)用android.net.Uri.getLastPathSegment()獲取paramUri路徑的最后一段,即文件名稱。此文件存在于預(yù)先配置的父目錄IMAGE_DIRECTORY中。

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

    然而,當(dāng)這個文件路徑被URL編碼后,就意味著被訪問的這個文件可能會存在于預(yù)配置的父目錄之外的一個不可預(yù)知的目錄中。

    從Android 4.3.0_r2.2開始, Uri.getLastPathSegment()方法在內(nèi)部調(diào)用了Uri.getPathSegments():

    public String getLastPathSegment() {// TODO: If we haven't parsed all of the segments already, just// grab the last one directly so we only allocate one string.List<String> segments = getPathSegments();int size = segments.size();if (size == 0) {return null;}return segments.get(size - 1);}

    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) {// This check keeps us from adding a segment if the path starts// '/' and an empty segment for "//".if (previous < current) {String decodedSegment = decode(path.substring(previous, current));segmentBuilder.add(decodedSegment);}previous = current + 1;}// Add in the final path segment.if (previous < path.length()) {segmentBuilder.add(decode(path.substring(previous)));}return pathSegments = segmentBuilder.build();}

    Uri.getPathSegments()?方法首先通過調(diào)用getEncoded()獲取了文件路徑,然后使用"/"作為分隔符將路徑分割成幾部分,任何被編碼的部分都將通過decode()方法進(jìn)行URL解碼。

    如果文件路徑被URL編碼,那么分隔符就變成了"%2F",而不再是"/",getLastPathSegment()就可能不會正確地返回路徑的最后一段,從而導(dǎo)致目錄遍歷漏洞的產(chǎn)生。

    如果Uri.getPathSegments()在進(jìn)行路徑分割之前對路徑進(jìn)行解碼,那么經(jīng)過URL編碼的路徑就會被正確地處理,可惜沒有這么實現(xiàn)。

    不合規(guī)代碼Example 2

    在本不合規(guī)代碼示例中,試圖通過調(diào)用Uri.getLastPathSegment()兩次來修復(fù)第一個不合規(guī)代碼示例中的漏洞。第一個調(diào)用意在進(jìn)行URL解碼,第二個調(diào)用是用于獲取開發(fā)人員需要的字符串。

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

    例如,當(dāng)以下的URL編碼字符串傳遞給content provider后會發(fā)生什么情況呢?

    ..%2F..%2F..%2Fdata%2Fdata%2Fcom.example.android.app%2Fshared_prefs%2FExample.xml

    第一次調(diào)用Uri.getLastPathSegment()函數(shù)會返回以下字符串:

    ../../../data/data/com.example.android.app/shared_prefs/Example.xml

    字符串通過Uri.parse()轉(zhuǎn)換成了Uri對象, 然后作為第二次調(diào)用Uri.getLastPathSegment()函數(shù)的參數(shù)。得到的結(jié)果如下:

    Example.xml

    這個字符串是用來創(chuàng)建一個文件對象的。然而,如果攻擊者提供了一個特殊的字符串,該字符串在第一次調(diào)用Uri.getLastPathSegment()時不能被解碼,那么就獲取不到分割路徑的最后一段。這樣的字符串可以使用雙重編碼技術(shù)創(chuàng)建:

    雙重編碼

    例如,下述雙重編碼字符串就能繞過該漏洞修復(fù):

    %252E%252E%252F%252E%252E%252F%252E%252E%252Fdata%252Fdata%252Fcom.example.android.app%252Fshared_prefs%252FExample.xml

    第一次調(diào)用Uri.getLastPathSegment()?會將 "%25" 解碼為"%",得到如下字符串:

    %2E%2E%2F%2E%2E%2F%2E%2E%2Fdata%2Fdata%2Fcom.example.android.app%2Fshared_prefs%2FExample.xml

    當(dāng)把這個字符串傳遞給第二次調(diào)用的 Uri.getLastPathSegment()時,"%2E"和"%2F" 就會被解碼,得到如下字符串:?

    ../../../data/data/com.example.android.app/shared_prefs/Example.xml

    從而導(dǎo)致目錄遍歷的可能。

    僅僅通過解碼字符串來防止示例中的目錄遍歷攻擊是不夠的,還必須檢查解碼后的路徑是否在目標(biāo)目錄下。

    PoC

    以下惡意代碼可對第一個不合規(guī)代碼示例中的漏洞進(jìn)行利用:

    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);

    PoC (Double Encoding)

    以下惡意代碼可對第二個不合規(guī)代碼示例中的漏洞進(jìn)行利用:

    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()對其進(jìn)行了解碼。同樣的,在文件對象創(chuàng)建后,通過調(diào)用File.getCanonicalPath()將路徑進(jìn)行了規(guī)范,同時檢查它是否存在于IMAGE_DIRECTORY目錄中。

    通過使用規(guī)范化后的路徑,即使文件路徑被雙重編碼,目錄遍歷漏洞也可以得到緩解。

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


    開發(fā)建議

    ContentProvider.openFile()方法提供了一種方便其它應(yīng)用程序訪問自己的數(shù)據(jù)(文件)的方式,但是使用這個方法會導(dǎo)致一個目錄遍歷漏洞。因此,當(dāng)通過ContentProvider訪問一個文件的時候,路徑應(yīng)該被規(guī)范化。

    1.使用ContentProvider.openFile()之前需要調(diào)用Uri.decode()

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

    2.設(shè)置exported=“false”

    3.設(shè)置恰當(dāng)?shù)脑L問權(quán)限

    ?

    原文地址:?http://www.cnblogs.com/goodhacker/p/5249122.html

    總結(jié)

    以上是生活随笔為你收集整理的Android应用安全之Content Provider安全的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。