
Вы когда-нибудь копировали в буфер обмена уязвимую информацию, например, пароли, номера кредитных карт, сообщения или личные данные? Если да, эти данные могут оставаться в буфере устройства достаточно длительное время. Доверяете ли вы буферу обмена и приложениям, получающим доступ к этим данным? В этой статье мы изучим 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. Он ограничил доступ к считыванию значений из буфера обмена без уведомлений для всех приложений, но оставил его для себя. Доступ компании к буферу обмена позволяет ей иметь ценный источник информации для таргетированной рекламы и сохранения позиции на рынке.
Узнавайте о новых акциях и промокодах первыми из нашего Telegram-канала ?

