• Android Content Provider Security(转)


    四大组件之一—content provider安全详解

    原帖地址: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" (增删改查)功能

    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 安全建议


    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

    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:直接暴露

    案例2:需权限访问

    案例3:openFile文件遍历

    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

  • 相关阅读:
    [Swift]LeetCode795. 区间子数组个数 | Number of Subarrays with Bounded Maximum
    [Swift]LeetCode794. 有效的井字游戏 | Valid Tic-Tac-Toe State
    [Swift]LeetCode793. 阶乘函数后K个零 | Preimage Size of Factorial Zeroes Function
    [Swift]LeetCode792. 匹配子序列的单词数 | Number of Matching Subsequences
    [Swift]LeetCode1012. 至少有 1 位重复的数字 | Numbers With 1 Repeated Digit
    [Swift]LeetCode1011. 在 D 天内送达包裹的能力 | Capacity To Ship Packages Within D Days
    [Swift]LeetCode1010. 总持续时间可被 60 整除的歌曲 | Pairs of Songs With Total Durations Divisible by 60
    [Swift]LeetCode1009. 十进制整数的补码 | Complement of Base 10 Integer
    [Swift]LeetCode791. 自定义字符串排序 | Custom Sort String
    转 OGG add trandata 到底做了什么
  • 原文地址:https://www.cnblogs.com/fishou/p/4182150.html
Copyright © 2020-2023  润新知