Несколько месяцев назад моему коллеге предстоял долгий перелёт — около 8 часов. Ему быстро стало скучно, и он обратил внимание на экран мультимедийной системы в спинке кресла напротив. Коллега запустил карту полёта и обнаружил, что это Android‑устройство с включённым Kiosk Mode. Этот режим должен помешать (или, по крайней мере, стать серьёзной проблемой) исследованию устройства, но на практике всё оказалось совсем наоборот. Коллега смог без каких‑либо трудностей обойти Kiosk Mode на планшете и прочитать файл /etc/shadow. Именно так появилась идея изучить Kiosk Mode глубже и разобраться: действительно ли он так безопасен?

В этой статье я постараюсь объяснить, как устроен Kiosk Mode в Android изнутри, расскажу об основных компонентах, необходимых приложению, чтобы превратить ваше устройство в kiosk‑девайс, а также приведу несколько примеров реальной эксплуатации kiosk‑устройств и приложений.

Данный материал впервые был представлен в качестве доклада на zeronight.

Что такое Kiosk Mode

Начнём с простого: что вообще такое Kiosk Mode?

Kiosk Mode — это режим на различных платформах (Windows, Linux, Android), предназначенный для ограничения возможностей пользователя на устройстве одним или несколькими приложениями. Говоря простыми словами, при использовании Kiosk Mode мы пытаемся «запереть» пользователя в заранее выбранных приложениях. При этом очень важно, чтобы пользователь оставался в этих приложениях. Выход из Kiosk Mode в самом безвредном случае полностью «ломает» функционал устройства, а в худшем — может привести к финансовым потерям или компрометации всей локальной сети, в которой находится kiosk‑устройство.

Kiosk Mode довольно широко распространён, хотя в сообществе ИБ о нём говорят не очень часто. Он используется в POS‑терминалах (недавно вышла статья, где автор подробно разбирает такое использование Kiosk Mode), постаматах, кассах самообслуживания, в общественном транспорте, в ресторанах в качестве электронного меню, в промышленных терминалах и в корпоративных устройствах. Но несмотря на важность безопасности Kiosk Mode, ею часто пренебрегают.

В данной статье не будет рассматриваться реализация Kiosk Mode под Linux или Windows: я сконцентрируюсь только на реализации Kiosk Mode в Android.

Устройства с Kiosk Mode можно условно поделить на две группы: Single App и Multi App.

Как можно понять из названия, Single App используется, когда возможности пользователя ограничены одним конкретным приложением. Single App обычно применяется для цифровых вывесок, мобильных POS‑систем, терминалов в аэропортах, терминалов в индустрии гостеприимства, а также терминалов бронирования билетов. Несмотря на то, что режим Single App проще как в установке на устройство, так и в администрировании, он используется реже.

В режиме Multi App доступ пользователя к устройству ограничен несколькими приложениями; при этом он может свободно переключаться между ними, что кратно расширяет функциональность устройства с Kiosk Mode. Multi App Kiosk обычно используется в сфере услуг, в образовательных целях, а также в компаниях, занимающихся доставкой и производством.

Kiosk-приложение на Android

В этой статье я не буду подробно описывать процесс разработки приложения для Kiosk Mode. Информацию для этого раздела я взял из Github‑статьи mrugacz95. Здесь я расскажу только об основных механизмах обеспечения «изоляции» пользователя, используемых в kiosk‑приложениях на Android.

В приложении, которое полностью ограничивает возможности пользователя, можно выделить три основных компонента:

  1. Home Launcher — это главный экран Android: на нём расположены иконки приложений, виджеты и прочие объекты, которые пользователь может туда поместить. Если наше приложение не будет лаунчером по умолчанию, то после перезагрузки Kiosk Mode перестанет работать и устройство вернётся к своим стандартным функциям.

  2. Screen Pinning. Закрепление экрана — это функция, позволяющая отображать одно приложение до момента его открепления. Она появилась в Android 5.0 и изначально предназначалась для передачи телефона другим людям без риска утечки информации. После включения этой функции на устройстве происходят следующие изменения: статус‑бар полностью скрывается, отключаются кнопки «Домой» и «Недавние приложения», а также вводится запрет на запуск новых действий другими приложениями.

  3. Device Admin. Приложение становится администратором устройства. Если пропустить этот шаг, мы не сможем полностью ограничить пользователя в доступе к уведомлениям и строке состояния, а также не сможем менять системные настройки из приложения, что для kiosk‑устройств крайне полезно.

Home Launcher

Чтобы сделать приложение лаунчером, нужно выдать ему интенты, написать соответствующий intentFilter и выдать необходимые привилегии с помощью addPersistentPreferredActivity.

Далее необходимо отключить Keyguard (экран блокировки). Это позволит нашему приложению запускаться сразу, обходя экран блокировки. Кроме того, необходимо предотвратить выключение экрана. Для этого следует выставить флаг FLAG_KEEP_SCREEN_ON для текущего окна (window принадлежит классу Acitivity и указывает на текущее окно).

Screen Pinning

Для функционирования Screen Pinning в Android существует специальный механизм Lock Task Mode (режим закрепления задач). Он позволяет закрепить Activity поверх экрана. Для закрепления приложения используются две функции: setLockTaskPackages() в DevicePolicyManager и startLockTask() в Activity.

setLockTaskPackages разрешает приложению закрепляться, а startLockTask обеспечивает закрепление. Финальным шагом закрепления приложения является добавление кода, обеспечивающего включение полноэкранного режима для нашего приложения.

Это основные механизмы, необходимые для включения Kiosk Mode со стороны разработчика приложений. Теперь давайте углубимся в Kiosk Mode и посмотрим изнутри, как работает изоляция приложений.

Kiosk Mode изнутри

setLockTaskPackages

Как уже было сказано в предыдущем разделе, функция setLockTaskPackages нужна для того, чтобы система позволила приложению закрепиться. Для этого используется механизм политик, а точнее DevicePolicyEngine (DPE) — внутренний механизм Android, который регламентирует управление политиками.

Суть работы DPE довольно проста:

  1. Каждый администратор по‑своему настраивает систему (например, разрешает или запрещает включение камеры).

  2. DPE на основе заранее заданных правил определяет, какая политика будет принята, после чего формирует итоговую политику, которая будет применена системой.

Например, в случае с камерой более приоритетным для Android является запрет. Поэтому, если два разных администратора устройства, равные в правах, потребуют одновременно разрешить и запретить доступ к камере, DPE применит политику запрета.

setLockTaskPackages как раз и задаёт такую политику. Сама функция реализована в DevicePolicyManagerService и используется для создания политики «это приложение (или эти приложения) могут быть закреплены». Ниже представлен исходный код функции.

Проанализировав эту функцию, можно заметить два основных вызова: getLocalPolicySetByAdmin и setLocalPolicy.

getLocalPolicySetByAdmin используется для чтения политики, заданной администратором для текущего пользователя. Использование этой функции позволяет обновлять политики, не заменяя собой «вклад» других администраторов: мы меняем только своё локальное значение, а затем DPE объединяет все источники и пересчитывает итоговую политику, которая будет использована далее. Заодно по этому значению удобно понять, было ли фактическое изменение: если изменения не было, можно избежать лишних действий и уведомлений.

setLocalPolicy — центральная точка записи локальной политики от конкретного администратора. Метод добавляет «вклад» администратора в политику, пересчитывает итоговое значение с учётом «вклада» других администраторов и при необходимости применяет изменения к системе, уведомляет администратора и распространяет политику на наследуемые профили.

Запуск Lock Task Mode

Запуск Lock Task Mode происходит в функции startLockTask из класса Activity, которая, в свою очередь, обращается к startLockTaskModeByToken. Это приводит к вызову функции startLockTaskMode в классе LockTaskController. Именно её и стоит рассмотреть подробнее.

Сперва следует обратить внимание на проверку параметра isSystemCaller. Он определяет, кто пытается закрепить приложение. При вызове из startLockTask этот параметр всегда равен false. Функционал внутри блока if (!isSystemCaller) отвечает за всплывающее окно, в котором пользователь устройства должен подтвердить закрепление (функция showScreenPinningRequest).

После подтверждения закрепления со стороны пользователя функция startLockTaskMode вызывается снова, но параметр isSystemCaller уже будет передан как true, и будет вызвана основная функция закрепления приложения — setLockTaskMode в классе LockTaskController.

При анализе setLockTaskMode основное внимание стоит уделить трём функциям: findTaskToMoveToFront, resumeFocusedTasksTopActivities и executeAppTransition.

Немного об окнах в Android

Для дальнейшего понимания процесса необходимо сделать небольшое отступление и объяснить, зачем в Android нужен класс Task и как он связан с приложениями, которые видит пользователь.

Task — это контейнер стека Activity. Этот класс хранит упорядоченный набор ActivityRecord. Когда пользователь переключается между приложениями или между экранами внутри одного приложения, система выбирает верхний (последний) ActivityRecord в этом списке. Этот верхний ActivityRecord считается текущим: его Activity должна оказаться на экране в состоянии RESUMED, а записи ниже в этом же списке переводятся в состояния PAUSED или STOPPED.

При обычном вызове startActivity() в список ActivityRecord текущего Task добавляется новая запись сверху, и тем самым верх стека меняется. При нажатии Back верхняя запись удаляется из списка, и её место занимает предыдущая запись. Если включены специальные правила (например, singleTop), новый элемент может не добавляться: вместо этого система использует существующий верхний ActivityRecord и обновляет его Intent. Если используется CLEAR_TOP или singleTask, система находит нужный ActivityRecord внутри списка этого Task, удаляет все записи выше него и делает найденную запись верхней. Если пользователь открывает другое приложение, стек не перестраивается: просто активным становится верхний Task.

Также Task является узлом в дереве WindowManager. Это означает, что Task существует как оконный контейнер (WindowContainer), который участвует в компоновке, отрисовке и Z‑порядке — очерёдности наложения элементов по оси глубины экрана (что выводится на передний план, а что остаётся на заднем). Если два окна или контейнера перекрываются, элемент с более высоким Z‑порядком будет находиться на переднем плане и обычно именно он получает фокус ввода.

В иерархии WMS (WindowManagerService) Task располагается ниже RootTask. Общая иерархия внутри WMS выглядит так: DisplayContent → TaskDisplayArea → RootTask → Task → ActivityRecord → окна/Surface.

RootTask — это верхнеуровневый контейнер задач в WindowManager. Он группирует обычные Task по режиму и политике отображения (например, fullscreen, split‑screen, PiP, freeform). Именно RootTask определяет общий оконный режим и границы для своих дочерних задач, а также управляет их порядком отображения на дисплее.

Как узел WM‑дерева Task задаёт границы для всего содержимого, наследует или переопределяет оконный режим (fullscreen, split‑screen, freeform) и является единицей, которую можно целиком перемещать в Z‑порядке (например, move‑to‑front — поднять задачу над соседями в том же RootTask). Кроме того, на уровне Task удобно применять групповые операции для отрисовки: показывать временную поверхность (starting window или snapshot), выполнять анимации и трансформации, а также делать reparenting — переносить задачу в другой RootTask или даже в другой DisplayContent (перенос на другой дисплей).

Возвращение в Kiosk Mode

Теперь, когда мы понимаем, как работает показ Activity в Android, можно вернуться к Kiosk Mode, а точнее к функции findTaskToMoveToFront.

findTaskToMoveToFront реализует фазу move‑to‑front для Task: она переводит выбранный Task на передний план и гарантирует, что его верхняя ActivityRecord будет показана на нужном дисплее, в нужном оконном режиме (обычно fullscreen), а также станет первой по фокусу и вводу. Этапы, которые выполняются в findTaskToMoveToFront, можно описать так:

  1. Reparenting — если Task находится в неподходящем оконном режиме (split‑screen, PiP) или на другом дисплее, его переносят в целевой полноэкранный root, чтобы получить единое фокусное окно.

  2. Подготовка перехода в WM — перед фактическим поднятием на передний план нужные ActivityRecord помечаются для корректной анимации и правильных Surface‑транзакций.

  3. Move‑to‑front и focus Task поднимается на вершину в иерархии задач, а фокус ввода переводится на его верхнюю Activity.

  4. Starting window (при необходимости) — если верхняя Activity ещё не готова к отрисовке, открывается стартовое окно, чтобы избежать «чёрного экрана».

  5. Передача управления фазе RESUME — после того как Task стал первым в очереди и получил фокус ввода, управление передаётся логике, которая переводит верхнюю Activity в состояние RESUMED.

resumeFocusedTasksTopActivities проходит по всем зонам дисплея (если их несколько) и для каждой области проверяет, какой RootTask сейчас считается главным и должен получать фокус.

После этого метод не пытается напрямую перевести в состояние RESUMED конкретную Activity, а просто делегирует работу на нижний уровень: выбранному RootTask передаётся команда «переведи в состояние RESUMED верхний ActivityRecord» и дальше уже внутри этого RootTask выбирается нужный Task, а внутри него — верхний ActivityRecord.

Внутри корня resumeTopActivityUncheckedLocked выполняет несколько проверок (состояние дисплея, состояние контейнера), после чего выбирается конкретная верхняя ActivityRecord и инициируется её переход в стадию RESUME.

Ключевая работа выполняется в TaskFragment.resumeTopActivity. Если Activity уже находится в состоянии RESUMED и привязана к процессу, метод просто актуализирует её видимость и завершает выполнение. В противном случае он переводит предыдущую Activity в состояние PAUSED и инициирует вызов onResume для текущей верхней Activity. Для этого в процесс предыдущей Activity передаётся PauseActivityItem, а в процесс новой Activity — ResumeActivityItem.

После того как Activity приложения будет подготовлена и перейдёт в состояние RESUMED, WindowManager переходит к стадии визуального применения изменений. Для этого вызывается executeAppTransition. Сначала метод сообщает TransitionController, что текущий DisplayContent готов запускать переходы, затем проверяет, задан ли вообще переход в mAppTransition. Если переход задан, он помечается как готовый к выполнению, после чего вызывается mWindowPlacerLocked.requestTraversal.

Вызов requestTraversal запускает следующий этап расстановки окон и поверхностей (layout и surface placement): на этом шаге изменения отображаются на экране и воспроизводится анимация (при её наличии).

Популярные ошибки при создании kiosk‑устройств

Теперь, когда мы в общих чертах разобрались, как работает Kiosk Mode, пора поговорить о том, как сделать свои kiosk‑приложения безопаснее (и на что обращать внимание в чужих решениях). В этом разделе будут рассмотрены часто встречающиеся ошибки в конфигурации приложений и устройств, работающих в режиме Kiosk Mode.

Лишние приложения в списке разрешённых приложений

Как было показано выше, при конфигурации kiosk‑устройств необходимо явно задавать список приложений, разрешённых к запуску. Довольно часто в этот список добавляют лишние приложения (например, браузер Chrome), что для Android‑устройства, основанного на Linux, может оказаться критичным. Доступ к браузеру открывает множество возможностей: от выхода из kiosk‑режима до исполнения кода на устройстве. Именно эта проблема была обнаружена в реализации Kiosk Mode упомянутого в начале статьи приложения: посредством нехитрых манипуляций с WebView приложения был запущен браузер и получен доступ к файловой системе. Кроме того, непроверенные сторонние приложения могут содержать собственные уязвимости и тем самым позволять обходить ограничения режима или менять системные настройки.

Недостаточная изоляция и экспорт компонентов

В конфигурациях Kiosk Mode с несколькими приложениями (Multi App) довольно часто возникает необходимость в их взаимодействии. Например, может потребоваться сервис для изменения системных настроек. Такой сервис обычно обладает большими привилегиями, чем обычные kiosk‑приложения. Если не ограничить к нему доступ извне (например, оставить компонент экспортируемым), атакующий может установить своё приложение или отправить кастомные интенты и использовать привилегированные компоненты: отключить Kiosk Mode или получить доступ к конфиденциальной информации.

Включённые параметры разработчика и ADB

К сожалению, иногда при работе с kiosk‑устройствами разработчики забывают отключить параметры разработчика или ADB. Поскольку у пользователя почти всегда есть физический доступ к kiosk‑устройству, разрешённая отладка может стать серьёзной проблемой. Она позволяет устанавливать и запускать сторонние APK, а также вмешиваться в работу системы.

Например, в случае CVE-2023-41255 и CVE-2023-43488 на промышленных терминалах Bosch ctrlX WR21 в системе был оставлен файл su, что позволяло почти без усилий получить root‑доступ.

Использование HTTP и отсутствие шифрования

Некоторые приложения в мультимедийных киосках используют открытые протоколы для получения конфигурации. Например, в CVE-2023-45220 и CVE-2023-45321 клиентское приложение на Android передавало по HTTP IP‑адрес и учётные данные для подключения к MQTT‑брокеру. Злоумышленник, находящийся в той же подсети, мог перехватить эти данные и получить контроль над системой.

Другой пример — отсутствие проверки сервера при подключении к MQTT (CVE-2023-45851): клиент соединялся с брокером без аутентификации, из‑за чего его можно было заставить подключиться к вредоносному серверу и принимать поддельные команды.

Открытые USB‑порты и клавиатурные атаки

При наличии открытых USB‑портов атакующий может использовать техники класса BadUSB: подключить устройство, которое эмулирует клавиатуру, автоматически вводит команды, запускает приложения или открывает системные экраны. В результате можно вывести устройство из kiosk‑режима, получить доступ к системным настройкам или запустить вредоносный код.

Разумеется, это не все ошибки. На практике их гораздо больше, и перечислить все почти невозможно. Я лишь хотел показать наиболее популярные и часто встречающиеся проблемы. А теперь я приведу несколько примеров уязвимостей в kiosk‑устройствах.

Уязвимости в kiosk‑устройствах

CVE-2023-21189 / CVE-2025-26428

В методе startLockTaskMode для задачи которая уже закреплена, код продолжал выполнять логику закрепления, не проверяя список mLockTaskModeTasks на наличие этой задачи. В результате, находясь в режиме Kiosk Mode, можно было вызвать stopLockTaskMode и повторно показать диалог «Закрепить приложение», а затем обойти ограничения Kiosk Mode и разблокировать устройство.

Уязвимость была исправлена лишь в одном из последних коммитов Android 15 с помощью дополнительной проверки:

CVE-2025-5344

Компания Bluebird выпускает множество различных устройств: телефоны, платёжные терминалы, мобильные компьютеры, корпоративные планшеты, интерактивные киоски самообслуживания, RFID‑устройства, Bluetooth‑сканеры штрихкодов, автомобильные и телематические устройства (vehicle gateway), а также мобильные принтеры. Среди них есть несколько устройств, работающих в режиме Kiosk Mode, в которых используется специальный Home Launcher.

В этом лаунчере был реализован AIDL‑сервис для управления настройками системы, который был экспортирован без надлежащей защиты. В результате любое приложение или локальный процесс на устройстве могли подключиться к этому сервису и изменять системные настройки. Эта уязвимость позволяла отключать политики безопасности или изменять критически важные параметры системы, такие как режим разработчика или доступ к shell.

CVE-2023-45844

Bosch Rexroth выпускает промышленные HMI‑панели ctrlX‑HMI‑WR21, которые работают на Android и часто используются как операторские терминалы в режиме Kiosk Mode: пользователю показывают только веб‑интерфейс через Google Chrome/Chromium. В уязвимых прошивках таких устройств ограничения реализованы так, что в браузере доступен стандартный системный путь установки приложений: пользователь с физическим доступом может довести Chrome до запуска системного установщика пакетов Android (Package Installer) и тем самым установить произвольный APK, хотя в Kiosk Mode такая возможность должна быть заблокирована. После установки своего приложения злоумышленник фактически «ломает» модель Kiosk Mode: может запускать код вне разрешённого сценария и добираться до критичных настроек устройства.

Вывод

К устройствам, работающим в режиме Kiosk Mode, нельзя относиться легкомысленно. Ошибка в конфигурации, лишнее приложение в allowlist, экспортированный компонент, включённый ADB или слабая сетевая защита способны свести на нет всю модель изоляции.

Именно поэтому безопасность kiosk‑устройств следует рассматривать как полноценную задачу защиты всей платформы, а не как простую настройку интерфейса или ограничение пользовательского доступа. Компрометация такого устройства нередко выходит далеко за пределы одного приложения или одного экрана: в ряде случаев она может привести к компрометации всей локальной сети организации, а выход из Kiosk Mode — открыть злоумышленнику широкие возможности для дальнейших действий.

Надеюсь, эта статья оказалась для вас полезной или хотя бы интересной и мне удалось немного погрузить вас в мир Android‑устройств, работающих в режиме Kiosk Mode.

Комментарии (2)


  1. NutsUnderline
    16.06.2026 09:08

    А может оказаться полезным, если захочется взять какое такое б.у. устройство и сделать из него каку нить панель управления, умным домом например..


  1. NutsUnderline
    16.06.2026 09:08

    стороннее замечание про zeronights. захотел скачать материалы, а оно лежит на яндкекс диске, а чтобы ее скачать - регистрация (телефон), пароль/faceid /отпечатки пальцев. Нельзя уже просто скачать файл...