Некоторые разработчики считают свои мобильные приложения безопасным хранилищем.
Когда вы смотрите на скомпилированный код приложения, кажется, что он не читается и никакие прописанные в нем значения не могут быть найдены. Однако опытный и целеустремленный пентестер обнаружит, что ключи безопасности и пароли действительно легко извлечь и даже автоматизировать процесс извлечения таких значений. Вот почему вы никогда не должны хранить или прописывать ключи внутри своего приложения.
Проблема рассмотрения скомпилированных мобильных приложений как безопасного хранилища вполне реальна. В этой статье утверждается, что 0.5% мобильных приложений содержат ключи AWS API, что приводит к раскрытию данных более 100 миллионов пользователей. Это число наверняка больше 0.5%, поскольку в этой статье рассматриваются только ключи AWS. Есть гораздо более важные ключи, которые не следует хранить непосредственно в коде, и вы можете найти множество статей, описывающих такие случаи. Также есть статьи CWE, описывающие эту тему.
Риск
Прописанные в коде конфиденциальные данные всегда могут утечь и могут быть использованы во вред вашему бизнесу. Ключи API могут предоставлять доступ к сторонним сервисам, таким как хранилище AWS, шлюз SMS, API платежей или аналитика. Хотя ключи аналитики не представляют большого риска, утечка любого из других упомянутых ключей может привести к серьезным последствиям.
Давайте посмотрим на несколько примеров.
- Ключи, обеспечивающие чтение данных из открытого бесплатного API
В таких случаях нет реального риска, поскольку запросы API бесплатны, а хранимые данные являются общедоступными. Злоумышленник может сам сгенерировать такой ключ для себя, поэтому у него нет причин использовать ваш ключ. - Ключи, обеспечивающие чтение конфиденциальных данных
Одним из лучших примеров здесь является ключ AWS API. Он может содержать разрешения на чтение, что позволяет злоумышленнику загрузить всю базу данных, хранящуюся там. В результате у вас будет утечка конфиденциальных данных и, как следствие, вы можете получить штраф за нарушение GDPR или других законов о защите персональных данных. Самый большой штраф за нарушение GDPR на настоящий момент составил 50 миллионов евро. - Ключи для доступа к SMS-шлюзам
Представим себе службу, в которой злоумышленник может отправить SMS, используя ваш ключ, и подписать всю вашу службу на рассылку спама, который будет приносить ему деньги за ваш счет. Это может привести к потере крупных сумм за короткое время. - Ключи для доступа к платежным сервисам
Давайте рассмотрим приложение, в котором есть ключ, который позволяет отправлять деньги в биткойн-кошелек или из него. Злоумышленник может найти этот ключ и отправить все деньги с вашего счета или даже использовать его в качестве цепочки для отмывания денег. - Серверный ключ облачных функций Firebase
Из официальной документации:
«Важно: не включайте ключ сервера в код клиента. Кроме того, убедитесь, что вы используете только серверные ключи для авторизации вашего сервера приложений. Ключи Android, iOS и браузерные отклоняются FCM».
Используя серверный ключ облачных функций Firebase, злоумышленник может отправлять уведомления каждому пользователю приложения. Такие уведомления могут, например, содержать вредоносные ссылки и устанавливать вредоносные приложения.
Решение
Не храните ключи в коде.
Если вам нужно взаимодействовать с чувствительным внешним API, создайте на бэкенде свой эндпоинт. Такая конечная точка должна быть аутентифицирована с помощью токена пользователя и соответствовать надлежащим требованиям безопасности. Таким образом, злоумышленники никогда не должны получить ключи API, и только вы сможете общаться со своим сервером.
Насколько сложно извлечь секретные данные из приложения?
Это может быть очень сложно, если приложение использует платные решения для обфускации кода и шифрования каждой строки в приложении. Но, в конце концов, получить их все равно будет можно.
С другой стороны, это может быть очень просто. Если в приложении нет обфускации, злоумышленник может автоматизировать поиск секретных ключей. После создания такого сценария злоумышленник сможет автоматически протестировать все приложения и найти множество таких секретных значений. Нужен будет только шаблон, который нужно будет искать. В простом сценарии можно будет создать словарь конфиденциальных ключевых слов, таких как key, secret и т.д., и искать эти строки внутри скомпилированного кода приложения или файлов, хранящихся внутри приложения, таких как plists или xml. Это простой и только один из многих сценариев. Другие сценарии могут быть действительно продвинутыми и покрывать лучше защищенные секреты. Вот почему так важно не хранить секретные ключи, которые могут вам навредить.
Чтобы полностью понять, что во многих случаях получить эти секреты не так уж и сложно, я рекомендую решать задачи Uncrackable, предлагаемые OWASP.
Android
Для Android вы можете ознакомиться с моими туториалами, в которых я объясняю все, от зондирования Android-приложений до создания автоматического скрипта Frida, который находит скрытые секреты на Uncrackable Level 1.
Вы также можете ознакомиться с другими руководствами в репозитории OWASP.
iOS
В этом разделе содержится руководство о том, как найти секретный ключ в приложении iOS.
Ключ как переменная
Распространенным решением, используемым многими разработчиками, является хранение секретного ключа как свойства.
private let topSecretKey = "top_secret_key_value"
Это самое простое решение для хранения секретного ключа в iOS, но его можно легко извлечь с помощью подходящего инструмента, например Hopper Disassembler..
Загрузите файл ipa в Hopper и нажмите кнопку Str в левом меню.
Затем найдите значение представленного ключа.
Вы можете видеть, что его легко найти, особенно когда название хранимого секрета имеет определенный шаблон, и вы можете искать строки, содержащие этот шаблон.
Ключ как массив Ints
Распространенным решением для хранения секретных строк является их шифрование и кодирование в массив Ints. Таким образом, мы не увидим их в разделе str в Hopper. Однако мы можем выполнить код класса, который его содержит, чтобы получить его свойства, используя инструмент динамического анализа, такой как Frida.
Для класса ObjC код Фриды будет выглядеть так:
var instance = ObjC.classes["ClassWithKeys"].alloc().init(); console.log("Class properties: " + instance["- _ivarDescription"]());
Он вернет нам все свойства и методы класса в String.
Хранение этих секретов в зашифрованном виде и использование другого класса для дешифрования немного затруднит получение секретов, но, в конце концов, приложению все равно потребуется место, где этот секрет попадет в память в расшифрованном виде.
Резюме
Вы не можете быть уверены, что жестко прописанные в коде ключи не утекут. Даже если они хранятся с использованием передового шифрования, обфускации и т.д. Мотивированные злоумышленники всегда смогут их извлечь.
Поэтому лучшее решение для хранения секретов — вообще не хранить их :)
Если вам нужно взаимодействовать со сторонним API, создайте для этого отдельную конечную точку на своем сервере.