Вы когда-нибудь копировали в буфер обмена уязвимую информацию, например, пароли, номера кредитных карт, сообщения или личные данные? Если да, эти данные могут оставаться в буфере устройства достаточно длительное время. Доверяете ли вы буферу обмена и приложениям, получающим доступ к этим данным? В этой статье мы изучим Android Clipboard Manager и продемонстрируем необходимость более качественной защиты копируемых данных.
Как устроен доступ к данным буфера обмена
В Android последний скопированный элемент сохраняется в основной клип. Любое приложение может сохранять текстовую информацию при помощи показанного ниже кода:
val clipboard: ClipboardManager? =
ContextCompat.getSystemService(this, ClipboardManager::class.java)
val clip = ClipData.newPlainText("", text)
clipboard?.setPrimaryClip(clip)
А вот, как приложение может её считывать:
val clipboard: ClipboardManager? =
ContextCompat.getSystemService(this, ClipboardManager::class.java)
clipboard.primaryClip?.getItemAt(0)?.text.toString()
Это глобальная переменная с методами get и set, ничего особо сложного. При копировании в буфер обмена данные остаются в нём, пока не будут перезаписаны новым значением или устройство не перезагрузится. Есть ли у этого процесса какие-то ограничения?
▍ До Android 12
Долгое время никаких ограничений не было. До Android 12 приложения могли получать доступ к данным буфера обмена, не уведомляя об этом пользователя, и даже читать их в фоновом режиме, что позволяло рекламным SDK собирать информацию об интересах и действиях пользователям в различных приложениях, особенно если он делился ссылками через буфер обмена.
▍ После Android 12
Эта проблема оставалась актуальной не только для Android, но и для iOS. Сначала Apple предложила если не решение, то хотя бы что-то, позволявшее пользователям осознать проблему. В iOS 14 небольшое окно показывает, что конкретное приложение считывает данные буфера обмена.
В Android 12 Google воспользовалась тем же решением и годом позже добавила похожий механизм: внизу экрана отображается небольшое окошко:
Взглянув на код, можно увидеть отрисовку простого всплывающего уведомления (которое знакомо всем разработчикам под Android):
Binder.withCleanCallingIdentity(() -> {
try {
CharSequence callingAppLabel = mPm.getApplicationLabel(
mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
String message =
getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
Slog.i(TAG, message);
Toast toastToShow;
if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
Drawable safetyProtectionIcon = getContext()
.getDrawable(R.drawable.ic_safety_protection);
toastToShow = Toast.makeCustomToastWithIcon(getContext(),
UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT, safetyProtectionIcon);
} else {
toastToShow = Toast.makeText(
getContext(), UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT);
}
toastToShow.show();
} catch (PackageManager.NameNotFoundException e) {
// do nothing
}
});
Также в коде присутствует список исключений для показа уведомления:
if (clipboard.primaryClip == null) {
return;
}
if (Settings.Secure.getInt(getContext().getContentResolver(),
Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
(mShowAccessNotifications ? 1 : 0)) == 0) {
return;
}
// Don't notify if the app accessing the clipboard is the same as the current owner.
if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
return;
}
// Exclude special cases: IME, ContentCapture, Autofill.
if (isDefaultIme(userId, callingPackage)) {
return;
}
if (mContentCaptureInternal != null
&& mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId)) {
return;
}
if (mAutofillInternal != null
&& mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
return;
}
if (mPm.checkPermission(Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION,
callingPackage) == PackageManager.PERMISSION_GRANTED) {
return;
}
// Don't notify if already notified for this uid and clip.
if (clipboard.mNotifiedUids.get(uid)) {
return;
}
К разрешению Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION
имеют доступ только системные приложения. То же самое относится к изменению Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS
. Однако к системным приложениям в устройствах Android относятся и предустановленные сторонние приложения. То есть эти приложения с лёгкостью могут обойти такую меру защиты.
Эта мера защиты работала в Android и iOS для всех остальных приложений. Сразу после обновления до Android 12 я заметил, что многие мои приложения считывают значение из буфера обмена, но вскоре после этого прекратили это делать.
Значит ли это, что существует какой-то способ обхода данного механизма? На первый взгляд, всё выглядит безопасно: сервис управляет созданием всплывающих уведомлений (toast) и общается с другими приложениями при помощи механизма межпроцессной коммуникации (inter-process communication, IPC) под названием Binder. Отменить или изменить отрисовываемое другим приложением всплывающее уведомление никак нельзя. Но можно ли отрисовать что-нибудь поверх него?
Благодаря системе безопасности Android — это всё-таки невозможно. Все отрисовываемые приложением окна по умолчанию будут находиться под системными окнами. Если только у приложения нет одного разрешения.
Разрешение SYSTEM_ALERT_WINDOW
Разрешение SYSTEM_ALERT_WINDOW позволяет отрисовывать приложения поверх остальных приложений. Вот несколько примеров:
- Приложения звонков: при поступлении вызова окно приложения звонка отрисовывается поверх всех остальных приложений, чтобы пользователь знал о звонке и мог на него ответить.
- Значки чатов Facebook*: при появлении в чате нового сообщения всплывает небольшое окно. Оно отрисовывается поверх всего, что есть на экране.
- Видеосообщения Telegram: кружок отрисовывается поверх экранов приложений и не пропадает при сворачивании приложения; видео продолжает воспроизводиться.
Разрешение необходимо дать в отдельном разделе экрана App Info.
Если задуматься, объяснение выглядит довольно пугающе.
Позволить этому приложению отображаться поверх других используемых приложений. Приложение будет видеть точки, которых вы касаетесь, и изменять отображаемое на экране
Зловредные программы использовали (а может, и продолжают использовать) это разрешение для отрисовки своих экранов поверх запускаемых банковских приложений и кражи учётных данных, введённых пользователем в поддельные окна.
Перерисовывать небольшое всплывающее уведомление при помощи SYSTEM_ALERT_WINDOW
— это как палить из пушки по воробьям. Но многие люди используют всплывающие функции мессенджеров наподобие Facebook* и Telegram, а также видеоприложений наподобие YouTube. Давайте проверим, что происходит, если у приложения есть это разрешение, и оно попробует отрисовать что-то поверх исходного всплывающего сообщения.
Сокрытие всплывающего уведомления
При наличии разрешения в целом идея проста: можно использовать LayoutInflater, чтобы выполнить inflate (создание объекта в коде) любого окна. После этого созданное окно передаётся WindowManager (отвечающему за отрисовку окон). Эту задачу выполняет показанный ниже код: он отрисовывает прозрачное окно со структурой, описанной в файле dummy_view.xml
.
val windowManager: WindowManager =
applicationContext.getSystemService(WINDOW_SERVICE) as WindowManager
val params = WindowManager.LayoutParams()
params.format = PixelFormat.TRANSLUCENT
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
params.isFitInsetsIgnoringVisibility = true
}
params.flags = (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
val view = LayoutInflater.from(applicationContext).inflate(R.layout.dummy_view, null)
windowManager.addView(view, params)
Давайте отрисуем на всплывающем уведомлении небольшой белый прямоугольник.
Несмотря на появление прозрачного белого прямоугольника, сообщение под ним остаётся видимым. Прямоугольник почти прозрачный из-за того, что параметр type WindowManager должен быть как минимум TYPE_APPLICATION_OVERLAY. Это минимальный тип для отрисовки поверх; остальные относятся к системным приложениям и приложениям звонков. В документации говорится следующее: «Система может в любой момент менять позицию, размер или видимость этих окон, чтобы снизить визуальную хаотичность и регулировать использование ресурсов».
Android снова обхитрил нас, сделав окно полупрозрачным. Однако давайте ещё раз подумаем над этой проблемой: у нас есть полупрозрачное окно. Как сделать его менее прозрачным? Очевидное решение — отрисовать поверх него несколько дополнительных окон. Давайте добавим поверх этого окна ещё два белых прямоугольника:
Сработало! Благодаря трём слоям, исходное всплывающее уведомление едва заметно, но при необходимости можно добавить ещё окон. При помощи небольших изменений они могут выглядеть как настоящее всплывающее уведомление. Благодаря Android и его опенсорсному коду это поведение можно быстро воспроизвести:
«Это приложение абсолютно точно не вставляет ничего из буфера обмена»
Поэтому можно перерисовать всплывающее уведомление или другим уведомлением, или любым другим окном. Полное сокрытие исходного уведомления позволяет не сообщать пользователю о действиях с буфером обмена. Любое приложение с разрешением SYSTEM_ALERT_WINDOW
может считывать данные из буфера обмена, не уведомляя об этом пользователя. Мы смогли убедиться в этом в самой свежей версии Android 14.
Стоит заметить, что некоторые поставщики уже предприняли меры для решения этой проблемы и не позволяют перерисовывать системные всплывающие уведомления. Например, конкретно этот способ не работает на новых устройствах Samsung с последними версиями One UI. Вы можете проверить, уязвимо ли ваше устройство, запустив представленное ниже демо.
Как запретить приложениям красть данные из буфера обмена в устройстве с Android
- Отключите разрешение SYSTEM_ALERT_WINDOW у максимального количества приложений. Несмотря на нашу юмористическую демонстрацию, предоставление этого разрешения может быть опасно, поэтому давайте его только тем приложениям, которым доверяете.
- Для защиты данных из буфера обмена не пользуйтесь устройствами с Android 11 и более старыми версиями. По возможности обновитесь до последней версии Android. Если это невозможно, то подумайте об использовании ROM на основе AOSP для Android 12+ (например, Lineage OS), которые применяются для продления срока жизни устройств.
- По возможности избегайте копирования уязвимой информации.
- Помните, что гиганты рекламного рынка всё равно без вашего ведома могут иметь доступ к данным буфера обмена в Android.
Демо-приложение
Мы подготовили простое приложение, позволяющее увидеть эту методику в действии. Можно использовать его для проверки того, уязвимо ли ваше устройство к этой атаке. Демо предназначено для устройств с Android 12 и выше. Его исходный код открыт и доступен для изучения.
Заключение
При работе с информационной безопасностью (как и с любой другой безопасностью) критически важно найти подходящий баланс между безопасностью и удобством применения. Слишком строгие меры могут отпугнуть пользователей от продукта, и тот же результат может ждать при отсутствии мер защиты. Это касается и буфера обмена. С точки зрения конфиденциальности и безопасности улучшенный механизм в Android должен, как минимум, походить на механизм в браузерах: запускаемый в браузерах код на JavaScript (не их расширения) может иметь доступ к данным буфера обмена только в случае действия, выполняемого пользователем.
Google реализовал в Android простую меру защиты, удерживающую пользователей от перехода на iOS. Он ограничил доступ к считыванию значений из буфера обмена без уведомлений для всех приложений, но оставил его для себя. Доступ компании к буферу обмена позволяет ей иметь ценный источник информации для таргетированной рекламы и сохранения позиции на рынке.
Автор:
ru_vds