Безопасность мобильных приложений: чек-лист по OWASP Mobile Top 10

Чек-лист защиты iOS и Android-приложений по OWASP Mobile Top 10 2024 и MASVS: учётные данные, токены и биометрия, хранение данных, pinning, проверка целостности, SDK, 152-ФЗ и инструменты тестирования.

Главное отличие мобильного приложения от сайта с точки зрения безопасности: код и данные клиента находятся в руках потенциального злоумышленника. Установочный файл можно скачать, декомпилировать, запустить на телефоне с root-доступом, перехватить трафик и подменить ответы сервера. Поэтому защита мобильного продукта строится из двух частей: не хранить и не доверять на устройстве того, что можно извлечь, и проверять на сервере всё, что пришло от приложения. В статье — чек-лист для разработчиков и технических руководителей: на какие стандарты опираться, как закрыть каждый риск из актуального OWASP Mobile Top 10, как хранить данные и токены на iOS и Android, как защитить сетевой обмен и сборку, какие инструменты использовать для проверки и что учесть в российском законодательстве. На что опираться: OWASP MASVS, MASTG и Mobile Top 10 У OWASP три связанных проекта для мобильной безопасности, и их полезно различать. MASVS (Mobile Application Security Verification Standard) — стандарт требований: что должно быть обеспечено. Актуальная версия — 2.1, в ней появилась отдельная категория приватности MASVS-PRIVACY. Уровней L1/L2 во второй версии больше нет — их заменили профили тестирования. MASTG (Mobile Application Security Testing Guide) — руководство по проверке: как тестировать каждое требование на iOS и Android. В 2026 году вышла стабильная версия MASTG 2.0. Mobile Top 10 — рейтинг самых значимых рисков для обучения и приоритизации. Действующая редакция — 2024 года, первое крупное обновление с 2016-го. Практическая схема: Top 10 — чтобы договориться о приоритетах, MASVS — чтобы записать требования в техническое задание, MASTG — чтобы проверить их перед релизом. OWASP Mobile Top 10 в редакции 2024 года M1: Improper Credential Usage — неправильное обращение с учётными данными. M2: Inadequate Supply Chain Security — небезопасная цепочка поставок. M3: Insecure Authentication/Authorization — небезопасная аутентификация и авторизация. M4: Insufficient Input/Output Validation — недостаточная проверка входных и выходных данных. M5: Insecure Communication — небезопасная передача данных. M6: Inadequate Privacy Controls — недостаточная защита приватности. M7: Insufficient Binary Protections — недостаточная защита исполняемого кода. M8: Security Misconfiguration — ошибки конфигурации. M9: Insecure Data Storage — небезопасное хранение данных. M10: Insufficient Cryptography — недостаточная криптография. Ниже — что стоит за каждым пунктом и как его закрыть. M1: учётные данные Типичные ошибки: ключ API стороннего сервиса или пароль тестовой учётной записи в коде, логин и пароль пользователя, сохранённые на устройстве для «автовхода», служебные токены в ресурсах сборки. Любая строка внутри приложения считается известной злоумышленнику. Обфускация замедляет, но не спасает. Секреты сторонних сервисов (платёжных, картографических с платными лимитами, AI-API) остаются на сервере; приложение обращается к своему бэкенду, а тот — к сервису. Пароль пользователя не хранится на устройстве. После входа хранятся только токены в защищённом хранилище. В репозитории работает сканирование секретов (например, gitleaks или TruffleHog), а утёкшие ключи отзываются, а не просто удаляются из кода. M2: цепочка поставок Мобильное приложение — это десятки SDK: аналитика, реклама, push-уведомления, чаты поддержки, платежи. Каждый получает те же права, что и ваш код, и может собирать данные или содержать уязвимость. Перечень зависимостей с версиями (SBOM) и автоматическая проверка на известные уязвимости в CI. Оценка SDK перед подключением: какие данные собирает, куда передаёт, кто поддерживает, как часто обновляется. Фиксированные версии зависимостей и проверка контрольных сумм; сборка на контролируемой инфраструктуре. Ключи подписи релизов — в защищённом хранилище с ограниченным доступом. Для Android удобно использовать Play App Signing, чтобы ключ подписи приложения хранился у Google, а у команды был ключ загрузки, который можно заменить. M3: аутентификация и авторизация Самые опасные ошибки здесь серверные: проверка прав только в интерфейсе приложения и отсутствие проверки, что запрашиваемый объект принадлежит пользователю. Приложение можно обойти и обращаться к API напрямую, поэтому каждое правило доступа проверяется на сервере. Этой части посвящена отдельная статья о безопасности API . На стороне приложения: Вход через внешнего провайдера — по OAuth 2.0 для нативных приложений (RFC 8252): авторизация во внешнем системном браузере или его безопасном компоненте (ASWebAuthenticationSession на iOS, Custom Tabs на Android), а не во встроенном WebView, и обязательно с PKCE (RFC 7636). Короткоживущий токен доступа и токен обновления с ротацией: при каждом обновлении выдаётся новый, а повторное использование старого — сигнал компрометации. Сессии на сервере можно отозвать: выход на всех устройствах, блокировка при смене пароля. Биометрия — способ разблокировать ключ на устройстве, а не проверка «да/нет» в коде. Результат вызова, который возвращает только логическое значение, подменяется инструментами динамического анализа. Надёжно — привязать доступ к секрету в Keychain или Android Keystore к биометрии, чтобы без неё секрет было невозможно получить. Защита от перебора — ограничение попыток входа и ввода кодов на сервере. SMS как второй фактор лучше, чем ничего, но уязвим к перевыпуску SIM-карты и перехвату; для чувствительных операций предпочтительнее push-подтверждение в приложении или одноразовые коды TOTP. Пример: токен обновления в Keychain, доступный только после биометрической проверки и только на этом устройстве. import Security func saveRefreshToken(_ token: Data) - OSStatus { var error: Unmanaged CFError ? guard let access = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .biometryCurrentSet, error ) else { return errSecParam } let lookup: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "ru.example.app", kSecAttrAccount as String: "refresh_token" ] SecItemDelete(lookup as CFDictionary) // удалить старое значение, если было var item = lookup item[kSecValueData as String] = token item[kSecAttrAccessControl as String] = access return SecItemAdd(item as CFDictionary, nil) } Флаг .biometryCurrentSet делает элемент недоступным, если набор отпечатков или лиц на устройстве изменился, а WhenPasscodeSetThisDeviceOnly запрещает перенос элемента на другое устройство и удаляет его при отключении код-пароля. На Android аналог — ключ в Android Keystore с setUserAuthenticationRequired(true) и использование его через BiometricPrompt с CryptoObject . M4: проверка входных и выходных данных Локальная база данных — только параметризованные запросы; в Room и Core Data это поведение по умолчанию, пока запросы не собираются конкатенацией строк. WebView — источник XSS и доступа к нативным функциям. JavaScript включается только там, где он нужен; мост между JavaScript и нативным кодом ( addJavascriptInterface , WKScriptMessageHandler ) доступен только страницам с доверенных адресов; загрузка произвольных адресов из внешних данных запрещена. Глубокие ссылки и интенты — внешние данные. Параметры проверяются, а действия, меняющие данные или деньги, требуют подтверждения пользователя. Для открытия приложения по ссылке лучше использовать проверенные домены: App Links на Android и Universal Links на iOS, а не только собственные схемы вида myapp:// , которые может перехватить другое приложение. Файлы и пути — проверка имён файлов при распаковке архивов и сохранении загрузок, чтобы исключить выход за пределы каталога приложения. Сервер проверяет всё повторно : проверки в приложении — для удобства пользователя, а не для безопасности. M5: передача данных Базовое правило — только TLS, без исключений для «внутренних» и тестовых адресов в релизной сборке. На iOS это обеспечивает App Transport Security, на Android начиная с версии 9 незашифрованный трафик по умолчанию запрещён; исключения в конфигурации должны быть осознанными. Закрепление сертификата: когда и как Pinning защищает от перехвата трафика через подложный доверенный сертификат, но добавляет операционный риск: если сертификат сервера сменится, а приложение ждёт старый ключ, пользователи потеряют доступ до обновления. Поэтому закрепляют открытый ключ, а не сертификат целиком, всегда держат резервный ключ и планируют ротацию. Для большинства приложений достаточно корректной проверки TLS; pinning оправдан для банковских, платёжных и других приложений с высоким риском. Современные платформы позволяют настроить закрепление декларативно, без собственного кода проверки. Android — через Network Security Configuration: !-- res/xml/network_security_config.xml -- network-security-config base-config cleartextTrafficPermitted="false" / domain-config domain includeSubdomains="true" api.example.ru /domain pin-set expiration="2027-06-01" pin digest="SHA-256" основной-ключ-в-base64= /pin pin digest="SHA-256" резервный-ключ-в-base64= /pin /pin-set /domain-config /network-security-config iOS, начиная с версии 14, — через ключ NSPinnedDomains в Info.plist: key NSAppTransportSecurity /key dict key NSPinnedDomains /key dict key api.example.ru /key dict key NSIncludesSubdomains /key true/ key NSPinnedCAIdentities /key array dict key SPKI-SHA256-BASE64 /key string хеш-открытого-ключа-в-base64= /string /dict /array /dict /dict /dict Атрибут expiration на Android отключает закрепление после указанной даты — страховка на случай, если приложение давно не обновлялось. M6: приватность Минимум разрешений и запрос в момент, когда функция действительно нужна, с объяснением зачем. Минимум данных : не собирать то, без чего функция работает, особенно геолокацию, контакты и идентификаторы устройства. Без персональных данных в журналах , аналитике и отчётах о сбоях: телефон, email, номера документов маскируются до отправки. Требования магазинов : Apple требует от приложений и SDK манифест приватности (Privacy Manifest) с объяснением использования ряда системных API, а Google Play — заполненный раздел Data safety. Сведения в них должны совпадать с реальным поведением приложения, включая подключённые SDK. Удаление аккаунта из приложения — обязательное требование обоих магазинов для приложений с регистрацией. M7: защита исполняемого кода Цель — не сделать взлом невозможным, а сделать его дорогим и заметным. Любая клиентская защита обходится при достаточных усилиях, поэтому решения о доверии принимает сервер. Сжатие и обфускация : на Android включённый R8 в релизной сборке; для iOS — коммерческие обфускаторы, если модель угроз этого требует. Проверка целостности устройства и приложения на сервере : Play Integrity API на Android и App Attest (DeviceCheck) на iOS. Старый SafetyNet Attestation API полностью отключён 31 января 2025 года — если он остался в коде, проверка не работает. Обнаружение root, jailbreak, отладчика и инструментов вроде Frida — как сигнал для сервера и повод ограничить чувствительные операции, а не как единственная защита. Для финансовых и высокорисковых приложений — решения класса RASP, которые контролируют целостность во время выполнения. M8: конфигурация Релизная сборка без флага отладки ( android:debuggable выключен), без тестовых экранов, переключателей окружений и отладочных журналов. Компоненты Android (activity, service, receiver, provider) не экспортируются без необходимости; начиная с Android 12 атрибут android:exported нужно указывать явно для компонентов с intent-фильтрами. Резервное копирование ограничено: android:allowBackup="false" или правила dataExtractionRules для Android 12 и выше; на iOS файлы с чувствительными данными исключаются из резервной копии через isExcludedFromBackup . Серверная часть: закрытые административные панели и отладочные эндпоинты, ошибки без внутренних подробностей, CORS только для известных источников. M9: хранение данных на устройстве Первое решение — не хранить. Всё, что можно получить с сервера по запросу, не должно лежать на устройстве. То, что хранить нужно, распределяется так: Что iOS A