Весь наш мир построен на противоположностях. Если вы создаете свое устройство и продаете его, то всегда найдется тот, кто захочет его взломать. Цели у злоумышленника буду самыми разными, от попыток сделать клон устройства (привет Китаю) до шантажа конечных потребителей, что весьма ухудшит вашу репутацию с точки зрения изготовления надежных устройств. И чем популярна система на основе которой построено устройство, тем интереснее она злоумышленнику. В последнее время активно развивается сегмент одноплатных компьютеров, таких как Raspberry Pi, и множества других. Linux системы по распространенности использования во встраиваемых систем, вышли на первые места. Большая функциональность устройств, например наличие разных беспроводных интерфейсов коммуникаций, в совокупности с большими возможностями ОС Linux, привела к серьезной необходимости организации защиты устройства. Некоторые думают, что достаточно отключить учетную запись root и установить надежный пароль, но на само деле это только малая часть того, что следует сделать. Какие технологии и концепции используются для снижения рисков и реализации более безопасного устройства работающего на Linux узнаете под катом.
Безопасность — это снижение рисков. В РФ существуют стандарты ГОСТ Р ИСО/МЭК по информационной безопасности призванные обеспечить защиту компьютерных автоматизированных систем. Но в большинстве российских стандартов в этой области, нет детальной информации как строить систему защиты встраиваемых систем. Более детализированные руководства по безопасности выпускают ведомства на коммерческой основе. Поэтому воспользуемся руководящими документами Агентства Европейского союза по кибербезопасности (European Union Agency for Cybersecurity, ENISA). Далее, описанная концепция безопасности, основана на руководстве по организации защиты встраиваемых систем — Hardware Threat Landscape and Good Practice Guide (распространяется бесплатно).
Руководство вводит следующие понятия: Владельцы (owners) — собственники использующие продукт или услугу, такие как конечный пользователь, производитель, владелец бизнеса и т.д. В свою очередь владельцы нацелены на защиту своих активов (assets). Активом является какая-либо ценность в продукте или услуге (данные, программный код, репутация, и т.д.). Антагонистом в этой схеме выступает злоумышленник (threat actors), человек или абстрактный объект (хакер, конкурент, правительство и т.д.), который создает угрозу (threat) нацеленную на актив таким образом, чтобы это могло привести к ущербу. Для реализации угрозы, злоумышленник будет исследовать уязвимости (vulnerabilities), слабые места в системе с помощью вектора атаки (attack vector), метода или пути, используемого злоумышленником для доступа или проникновения в целевую систему.
На диаграмме ниже очень представлены данные концепции:
Термины и отношения, связанные с угрозами, в соответствии с ENISA Threat Landscape 2013
В итоге это походит на игру щит и меч, между владельцем и злоумышленником. Насколько далеко зайдет владелец, чтобы защитить активы? Как далеко зайдет злоумышленник, чтобы создать угрозу? Все это зависит от стоимости активов, которая может быть выражена в виде эквивалента денежных средств, репутации, безопасной обстановке, и т.д. Восприятие ценности может быть неодинаковым для владельцев и злоумышленника.
Определение активов (и их стоимости) для снижения рисков компрометации выполняется в ходе моделирования угроз (threat modeling).
При моделирование угроз выявляются потенциальные угрозы и расставляются по приоритету. По сути, это процесс оценки рисков, при котором происходит оценка стоимости активов и затраты на их защиту. Результатом моделирования угроз является модель угроз (threat model) вашего продукта:
Моделирование угроз
Существует различные методы и методологии, призванные помочь при моделировании угроз, включая STRIDE, DREAD, VAST, OCTAVE и многие другие.
Для общего понимания рассмотрим STRIDE и DREAD.
Модель STRIDE хорошо помогает классифицировать угрозы, была разработана в Microsoft. Название STRIDE является аббревиатурой от шести основных типов угроз:
Методология STRIDE
STRIDE можно использовать для выявления всех угроз, нацеленных на активы владельца:
Таблица 1. Методология STRIDE.
Методология DREAD призвана оценивать риски угроз компьютерной безопасности. Название представляет собой аббревиатуру пяти категорий угроз безопасности:
Таблица 2. Методология DREAD.
В то время как модель STRIDE помогает идентифицировать угрозы, методология DREAD помогает их ранжировать. Для каждой угрозы необходимо пройтись по категориям и выставить соответствующий бал: низкая вероятность (1 балл), средняя вероятность (2 балла), высокая вероятность (3 балла). По итогу, будет получен ранжированный список угроз и стратегий задач. Пример:
Таблица 3. Список угроз и стратегий задач
Моделирование угроз позволяет сформировать четкое понимание что необходимо защищать, каким образом планируется это защищать, и сколько будет стоить реализация защиты. Эту часть модели угроз продукта, необходимо переоценивать для каждого цикла разработки. В результате модель угроз будет содержать приоритетный список угроз, над которыми необходимо работать, так что можно сосредоточиться на реализации мер защиты для повышения безопасности продукта.
Как убедиться в том, что программный код, который используется для запуска системы не подложный? Для решения этой задачи необходимо обеспечить безопасный процесс запуска системы.
Целью обеспечения безопасного запуска системы является защита целостности и аутентичности загружаемых файлов. Защита процесса загрузки обычно основана на проверке цифровых подписей. Встраиваемая система Linux обычно состоит из трех основных компонентов: загрузчика, ядра и корневой файловой системы (rootfs). Все эти компоненты подписаны, и подписи проверяются во время загрузки.
Например, для проверки подписи загрузчика (bootloader) можно использовать какой-либо аппаратный механизм, реализованный в виде отдельной микросхемы или устройства. Аппаратный механизм в первую очередь проверяет цифровую подпись ядра, затем происходит загрузка виртуального диска (ramdisk image), который в свою очередь проверяет цифровые подписи корневой файловой системы (root filesystem). Поскольку у нас есть один компонент, проверяющий цифровую подпись следующего в цепочке загрузки, этот процесс часто называют цепочкой доверия (chain-of-trust).
Пример, как реализована цепочкой доверия (chain-of-trust) на устройстве NXP iMX6
Все начинается с запуском исполняемого кода из ПЗУ (ROM) в SoC. В NXP iMX6 есть аппаратный компонент под названием High Assurance Boot (HAB), который проверяет на первом этапе цифровую подпись загрузчика (bootloader), что позволяет реализовать процесс безопасной загрузки устройства. High Assurance Boot на устройствах iMX6 так же можно назвать Root of Trust (корневое доверие), поскольку если компонент будет скомпрометирован, то весь дальнейший процесс безопасной загрузки также будет скомпрометирован.
Компоненту HAB для проверки цифровой подписи загрузчика (bootloader) необходимо сгенерировать пару ключей (открытый и закрытый). Загрузчик подписывается закрытым ключом, а внутри SoC сохраняется открытый ключ.
Внутри чипа iMX6 находится память однократной записи (One-time programmable memory) предназначенная для хранения ключей, т.е. хранится хеш открытого ключа.
Загрузчик при запуске (например, U-Boot) проверяет подпись ядра Linux. Для этого обычно используется формат образа, называемый FIT image. FIT image представляет собой контейнер для бинарных файлов с поддержкой хеширования и цифровой подписи, и обычно содержит образ ядра Linux, файлы дерева устройств и стартовый ramdisk. После создания пары ключей, необходимо подписать бинарные файлы в FIT image с помощью закрытого ключа, и настроить U-Boot для использования открытого ключа для проверки подписи FIT image.
После загрузки ядра Linux запустится программа инициализации из образа ramdisk. ramdisk содержит логику для окончательной проверки целостности корневой файловой системы перед ее монтированием. Существует несколько вариантов реализации данной задачи. Один из наиболее распространенных вариантов — использование модуля ядра device-mapper verity (dm-verity). Модуль ядра dm-verity обеспечивает проверку целостности блочных устройств и требует наличия rootfs только для чтения. Другой вариант заключается в использование IMA или dm-integrity, если необходимо монтировать корневую файловую систему в режиме чтение-запись.
Схема полного процесса безопасной загрузки.
Это только один пример реализации безопасной загрузки, так же данный подход может быть применен к другим платам и ARM SoC.
При реализации защиты необходимо помнить что 100% безопасности не существует.
17 июля 2017 г. была публично раскрыта уязвимость в ROM используемой для безопасной загрузке в нескольких устройств NXP (семейства i.MX6, i.MX50, i.MX53, i.MX7, i.MX28 и Vybrid). И если ваша цепочка доверия скомпрометирована, то все скомпрометировано! Поэтому, после реализации системы защиты необходимо в течение всего срока эксплуатации устройства, следить за публикациями уязвимостей.
Хотя безопасная загрузка обеспечивает аутентичность и целостность исполняемого кода, она не защищает устройство от считывания исполняемого кода/данных злоумышленником. Поэтому, для защиты своей интеллектуальной собственности или обеспечения конфиденциальность данных, необходимо использовать шифрование.
Для защиты конфиденциальных данных системы и пользователей необходимо обеспечить шифрование на самом устройстве. Данные — это любая информация, созданная во время работы устройства, включая базы данных, файлы конфигурации и так далее.
Не всегда требуется реализовывать шифрования исполняемых файлов, а полное шифрование корневой файловой системы встречается очень редко. Большинство используемых компонентов — это бесплатное программное обеспечение с открытым исходным кодом, поэтому его не имеет смысла скрывать. Существует также проблема в лицензирование GPLv3 и Tivoization (использование любого программного обеспечения GPLv3 вынудит вас предоставить пользователю механизм для обновления программного обеспечения, и это усложнит задачу, если шифруете исполняемый код). Наиболее распространенный подход — шифрование только приложений, которые вы разработали для устройств. Обычно это ваша интеллектуальная собственность.
В Linux доступно два сценария использования шифрования данных: полное шифрование диска (full disk encryption) и пофайловое шифрование (file-based encryption).
Полное шифрование диска работает на уровне шифрование блоков, при этом зашифровывается весь диск или его раздел, для этого необходимо использовать dm-crypt.
Пофайловое шифрование работает на уровне файловой системы, где каждый каталог/файл может быть зашифрован отдельным ключом. Две наиболее распространенные реализации файлового шифрования — это fscrypt и eCryptFS. fscrypt — это API, доступное в файловых системах, таких как: EXT4, UBIFS и F2FS. eCryptFS — более общее решение, реализованное в виде дополнительного слоя, который накладывается поверх существующей файловой системы и не зависит от нее.
Шифрование это хорошо, но как быть с ключами, где и как их хранить?
Поскольку алгоритм с асимметричным ключом слишком медленный при использования для шифрования диска или файлов, то используется алгоритм с симметричным ключом. Это означает, что для шифрования и дешифрования используется один и тот же ключ. Причем этот ключ должен быть расположен где-то в файловой системе, чтобы можно было расшифровать и зашифрованный исполняемый код/данные.
Но оставлять ключ в файловой системе нельзя, верно?
В истории отметились некоторые случаи, когда компании проигнорировали этот момент и получили в дальнейшем проблемы. Например, для первой игровой консоли Xbox, Эндрю «bunnie» Хуанг, исследователь по безопасности, разработал устройство на базе FPGA для прослушивания коммуникационной шины и извлечения ключей шифрования из нее. Ключи были легко получены только потому, что они передавались в виде открытого текста, без какой-либо защиты. По сути Эндрю сделал снифер и сделал дамп всех передаваемых данных. Кстати, Эндрю Хуанг является автором книги по взлому оборудования под названием «Взлом Xbox».
Подключение снифера к материнской плате Xbox
Защита зашифрованного исполняемого кода/данных так же надежна, как и защита ключа для их расшифровки! Итак, одна из проблем, с которыми сталкивается шифрование, — это хранение ключей.
Для настольных компьютеров или смартфонов, использующие шифрование файловой системы, ключ может быть получен из пароля пользователя (парольной фразы).
Во встраиваемых системах обычно нет интерактивного взаимодействия с пользователем для получения ключа из пароля каждый раз, когда происходит загрузка устройства. Таким образом, ключ должен храниться в зашифрованном виде в файловой системе или в безопасном хранилище.
Например, процессоры NXP i.MX содержат уникальный главный ключ (предварительно записанный NXP), доступ к которому может получить только специальный аппаратный модуль — CAAM (Cryptographic Accelerator and Assurance Module). Данный модуль используется для шифрования ключа и сохранения его в файловой системе. Во время загрузки устройства, модуль CAMM используется для расшифровки ключа и восстановления простого ключа, который будет использоваться для расшифровки файловой системы. Поскольку ключ внутри модуля CAAM недоступен, зашифрованный ключ защищен.
Если в процессор не интегрированы подобный функции безопасности, то можно воспользоваться внешними аппаратными модулями такими как Secure Element и TPM.
Secure Element — это безопасная компьютерная система, в состав которой входит безопасное хранилище с собственными защищенными приложениями (обычно реализуется с помощью Java Card). Что предоставляет возможность реализации различных алгоритмов шифрования, но в большинстве случаев интегрируют Стандарт шифрования с открытым ключом 11 (PKCS # 11). Secure Elements используется в смарт-картах и SIM-картах.
Доверенный платформенный модуль, или TPM (trusted platform module) – это отдельный микрочип на системной плате компьютера, разработанный согласно спецификации ISO/IEC 11889, и выполняет специфический круг задач, связанных с криптографией и защитой компьютера. TPM обеспечивает безопасное хранение данных, поэтому его можно использовать для хранения главного ключа, который в дальнейшем будет использоваться для шифрования/дешифрования ключа шифрования файловой системы. Помимо защищенного хранилища подобные устройства также предоставляют дополнительные функций безопасности, таких как генерация случайных чисел, вычисление хэшей, функции шифрования и подписи и т. Д. Так же TPM может быть реализован программно, но большинство реализации выполнено в виде аппаратного модуля.
Компания STMicroelectronics разработала модуль TPM STPM4RasPI для Raspberry Pi устройств. Данный модуль подключается на 40-контактный разъем и может работать по интерфейсу: I²C или SPI. Краткое руководство — Raspberry Pi extension board with an ST33 Trusted Platform Module.
TPM STPM4RasPI от STMicroelectronics
Модуль STPM4RasPI подключен к Raspberry Pi
После подключения модуля по интерфейсу SPI и установке драйверов, модуль STPM4RasPI обнаруживается в системе:
STPM4RasPI подключен к Raspberry Pi
Третьим вариантом обеспечения безопасного хранилища данных является использование доверенной среды исполнения (Trusted Execution Environment, TEE). TEE — среда для исполнения кода и хранения данных, которая надежно изолирована от основной ОС устройства. Существую различные варианты реализации TEE:
Многие устройства используемые в повседневной жизни используют надежную среду исполнения, включая смартфоны, телевизионные приставки, игровые консоли и Smart TV.
Если вы реализуете в своем встраиваемом устройстве шифрование, то вам придется подумать о хранении ключей и управлении ими с самого начала проекта.
Использование безопасной загрузки и шифрования данных недостаточно для комплексной защиты встраиваемых устройств на Linux. Система защиты должна быть многоуровневая, где на каждом уровне используются свои методы защиты данных. Сочетание нескольких уровней защиты затрудняет взлом устройства злоумышленником. Например, можно защитить исполняемый код и обеспечить шифрование данных, но если выше приложение работает с ошибками, которые могут быть использованы, то у вас будет проблемы с безопасностью. Если на приложение можно осуществить различные векторы атак (пользовательский ввод, файлы конфигурации, сетевые соединения и т. д.), то все предыдущие используемые методы защиты данных не помогут.
Если приложение подвержено векторам атак (пользовательский ввод, файлы конфигурации, сетевое взаимодействие и т. д.), то эти ошибки могут использовать эксплойты. В частности, программы написанные на небезопасных языках программирования таких как C/C++, применимы такие атаки как: переполнение буфера, разбиение стека и форматирование строк.
Как пример, в ядре Linux (с версии 2.6.34 до 5.2.x) была обнаружена ошибка переполнения буфера, при которой функциональность vhost переводила буферы виртуальной очереди в IOV. Проблема позволяет из гостевой системы создать условия для переполнения буфера в модуле ядра vhost-net (сетевой бэкенд для virtio), выполняемом на стороне хост-окружения. Атака может быть проведена злоумышленником, имеющим привилегированный доступ в гостевой системе, во время выполнения операции миграции виртуальных машин.
Фрагмент кода с vhost
Уязвимости был присвоен номер CVE-2019-14835 и исправлена в 2019 году. На практике пользователь виртуальной (гостевой) машины мог использовать эту уязвимость для получения root доступа на основной хост-машине. Уязвимость (и многие другие) присутствовала в ядре Linux в течение нескольких лет!
В конце концов, в программном обеспечении всегда будут ошибки, но мы можем попытаться свести их к минимуму. И для этого мы можем использовать инструменты статического анализа.
Инструменты статического анализа позволяют анализировать исходный код (без запуска программы), для нахождения проблем до того, как вы столкнетесь с ними во время исполнения. Эти инструменты обнаруживают программные ошибки, такие как утечки памяти, целочисленное переполнение, выход за пределы массива, и многое другое!
Существует множество различных инструментов с открытым исходным кодом (cppcheck, splint, clang и т. д.). И коммерческие варианты, такие как популярный PVS-Studio. Компиляторы обычно содержат встроенный инструмент статического анализа, который во время компиляции выдает предупреждения или ошибки. Вот почему мы никогда не должны игнорировать предупреждения компилятора.
Итак, первый шаг к минимизации риска потенциальных уязвимостей в приложениях — никогда не игнорировать предупреждения компилятора и использовать инструменты статического анализа. Но некоторые проблемы очень сложно, а иногда и невозможно выявить на уровне исходного кода. Вот почему нам может потребоваться добавить защиту приложения по время его выполнения.
Защита во время выполнения препятствует динамическому анализу приложения. Целью злоумышленника является адресное пространство в памяти используемой приложением. Например, злоумышленник может из памяти приложения считать ключи и пароли в открытом виде, или изменить некоторые данные. Многие из нас еще в детстве уже были хакерами и изменяли память процесса с помощью программы ArtMoney. Путём внедрения в процесс или в файл игры, ArtMoney обнаруживает ячейки адресов памяти, которые отвечают за хранение пользовательских настроек игры: время (таймер), количество денег, силы, жизней, патронов и т. д. Таким образом, можно было бесплатно себе обеспечить бесконечные патроны, максимум игровой валюты, или бесконечную жизнь.
Для защиты доступа к адресному пространству приложения используют подобные методы и инструменты:
Использование средств защиты это всегда выбор компромиссов, например использование ASLR снижает общую производительность системы, другие инструменты осложняют отладку и диагностику приложения.
Фаззинг — это техника автоматизированного тестирования, при которой на вход программе подаются специально подготовленные данные, которые могут привести её к аварийному состоянию или неопределённому поведению. Для проведения фаззинга существует множество бесплатных инструментов с открытым исходным кодом, такие как AFL и syzkaller (фаззер ядра Linux). Данные программы многие исследователи по безопасности и злоумышленники, используют для поиска уязвимых мест с точки зрения безопасности в программном обеспечении. В некоторых случаях исследователи разрабатывают свои инструменты для фаззинга, и это позволяет им зарабатывать большие деньги на платформах BugBounty, обнаруживая ошибки в программном обеспечении.
Такие методы и инструменты как фаззинг, статический анализ и защита во время выполнения, помогут значительно уменьшить количество ошибок в программном обеспечении. Но это не значит, что вы можете на этом остановится! В программном обеспечении всегда есть и будут ошибки, поэтому нужен еще один уровень защиты на случай, если программа будет взломана. И это приводит нас к системе разграничения прав доступа.
Один из способов уменьшить ущерб при эксплуатации уязвимости — не запускать программы с привилегиями root (суперпользователя)! Вы должны воспользоваться системой разграничения прав доступа и запускать процессы от имени непривилегированных пользователей, которые должны быть добавлены в группы с доступом только к тем ресурсам, которые необходимы для запуска.
Это называется принципом набора минимальных привилегий и является одним из правил разработки безопасных систем. Приложения должны запускаться только с теми привилегиями, которые необходимы для выполнения своей работы.
Но проблема заключается в том, что в Linux для выполнения некоторых привилегированных операций (например, настройка времени, использование RAW сокетов и т. д.) требуются права «root пользователя». В этом случае потребуется запустить программу от имени root, но это неправильный подход. Один из способов решить эту проблему использовать гранулированные права доступа, в Linux это называется — capabilities.
Гранулированные права доступа (Linux capabilities) — это детализированная система контроля доступа для процессов, выполняемых с привилегиями root.
Ядро Linux делит привилегии связанные с суперпользователем на отдельные единицы, известные как capabilities, которые могут быть независимо включены или отключены. Идея состоит в том, что программа будет работать от имени пользователя root, но при этом будет включать только те capabilities, которые необходимы для выполнения своей работы. В Linux для перечисления capabilities, которые требуются для запуска конкретной программе, используется инструмент — getcap:
$ getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw+ep
Предоставление процессу определенных гранулированных прав от пользователя root, может быть весьма избыточным, поэтому следует еще больше минимизировать набор выдаваемых прав с на основе мандатного управления доступом.
Linux традиционно поддерживает Дискреционное управление доступом (Discretionary Access Control, DAC). DAC — управление доступом субъектов к объектам на основе списков управления доступом или матрицы доступа. Субъект с определенным правом доступа может передать это право любому другому субъекту. Например, когда вы расписываете доступ к файлу, вы указываете: имя владельца файла (субъект), права на чтение, права на запись, права на запуск, для субъектов: пользователь или программа выполняющаяся под именем пользователя. Объектами права выступают: файлы, каталоги, и т.д.
Дискреционное управление доступом
Пример дискреционного управления доступом к файлам в Linux Ubuntu:
root@bananapim64:/# ls -l
total 72
drwxr-xr-x 2 root root 4096 May 12 22:11 bin
drwxr-xr-x 3 root root 4096 Jun 2 16:53 boot
drwxr-xr-x 16 root root 3660 May 30 01:08 dev
drwxr-xr-x 94 root root 4096 May 30 01:08 etc
drwxr-xr-x 3 root root 4096 Feb 4 11:33 home
drwxr-xr-x 16 root root 4096 May 13 21:36 lib
drwx------ 2 root root 16384 Feb 4 11:33 lost+found
...
Другой тип контроля доступа называется Мандатное управление доступом (Mandatory access control, MAC). MAC — разграничение доступа субъектов к объектам, основанное на назначении метки (мандата) конфиденциальности для информации, содержащейся в объектах, и выдаче официальных разрешений (допуска) субъектам на обращение к информации такого уровня конфиденциальности.
Мандатное управление доступом
Модель MAC реализована в ядре Linux на основе фреймворка — Linux Security Module (LSM). LSM — структура, которая позволяет ядру Linux поддерживать различные модели компьютерной безопасности. Двумя наиболее известными модулями безопасности в Linux, реализующими MAC, являются SELinux и AppArmor:
Но иногда использование MAC недостаточно для защиты системы от уязвимого приложения. В этом случае обеспечить должную защиту поможет запуск приложения в песочнице.
Песочница позволяет изолировать приложения от основной системы. Самый старый механизм реализации песочницы в Linux это — chroot. Но chroot позволяет изолировать только файловую систему, что не обеспечивает полную изоляцию приложения от системы. Можно использовать аппаратную виртуализацию для каждого приложения, но этот подход очень требователем к аппаратным ресурсам и не подходит для встраиваемых система. В настоящее время существует два решения для запуска приложений в песочнице, — это контейнеры и Trusted Execution Environments (TEE).
Контейнер Linux — представлен в виде минимальной файловой системы, содержащей только необходимые программные компоненты для запуска определенного приложения или группы приложений. Контейнер работает полностью изолированно от основной системы, и работает только на ядре Linux.
Технология контейнеризации приложения в Linux базируется на функция ядра Linux, таких как:
Для работы с контейнерами в Linux существуют менеджеры облегчая создание, эксплуатацию, изменение контейнеров, такие как: LXC, Systemd-nspawn, Podman и самый популярный Docker.
Использование контейнерезации приложений сам по себе не является гарантией защиты системы, требуется правильно ограничить разрешения каждого процесса внутри контейнера и контролировать обмен данными между ними, уменьшая вектор атак и повышая безопасность системы.
В случае использования Docker при создания файла сборки образа — Dockerfile, необходимо воспользоваться лучшими практиками — Best practices for writing Dockerfiles. Интересный проект Dockle позволяет выполнить статистический анализ файла Dockerfile и получить отчет о проблемных местах. Запускать Dockle можно путем использование утилиты, или из контейнера.
Полученный отчет безопасности от Dockle
Помимо Dockle существуют и другие инструменты для статистического анализа, более подробно можно ознакомится в публикации — 10+ top open-source tools for Docker security.
Комбинируя использование контейнеров вместе с модулем безопасности (например, AppArmor или SELinux), можно существенно повысить защиту системы.
В системе основанной на контейнерах, если ядро ОС будет скомпрометировано, то и вся операционная система окажется под угрозой. В этом случае доверенная среда выполнения — это еще один уровень безопасности, который может это предотвратить.
Доверенная среда выполнения (Trusted Execution Environment, TEE) — это среда, изолированная и защищенная с точки зрения конфиденциальности (никто не имеет доступа к данным) и целостности (никто не может изменить код) в которой исполняется код и хранятся данные.
В системе с доверенная среда выполнения (TEE) приложения делятся на:
Только доверенные приложения, работающие в TEE (Secure World), имеют полный доступ к основным аппаратным ресурсам, периферийным устройствам и памяти. Аппаратная изоляция защищает доверенные приложения (ТА) от ненадежных приложений, работающих в основной операционной системе (Non-Secure World).
Схема работы системы вместе с доверенной средой выполнения
Для использование доверенной среды выполнения (TEE) необходима поддержка на уровне аппаратного обеспечения, для возможности разделения и изолирования оборудования (шины, периферийные устройства, области памяти, прерывания и т. д.), чтобы предотвратить доступ ненадежных приложений к защищенным ресурсам. Эта функция уже присутствует в большинстве современных процессоров (например, TrustZone ARM, MultiZone RISC-V, Intel SGX).
Доверенная среда выполнения используют многие устройства, такие как: смартфоны, телевизионные приставки, игровые консоли и Smart TV. Существуют коммерческие реализаций TEE: Kinibi, QSEE и iTrustee. И реализации с открытым исходным кодом: Trusty и OP-TEE. TEE может быть хорошим решением для приложений-песочниц, хранения и управления ключами шифрования, хранения учетных данных и конфиденциальных данных и управления ими, а также защиты информации, защищенной авторским правом.
Несмотря на все меры, которые были рассмотрены выше, операционная система с миллионами строк кода обязательно будет содержать ошибки и уязвимости! А наличие системы обновлений очень важно для встраиваемых систем и подключенных устройств, где безопасность является ключевой особенностью продукта.
Система обновлений должна быть спроектирована на ранних этапах разработки продукта, по возможности с поддержкой OTA (обновление по беспроводным каналам связи, Over-the-Air). Использование системы обновления создает для некоторые проблемы для разработки продукта, включая безопасность протокола связи, атомарность процесса обновления, защиту от сбоев питания, использование полосы пропускания и хранилища, возможности отката и т. д.
Реализовать систему обновление можно в соответствие с основными стратегиями, такими как:
Необходимо помнить что использование OTA-обновлений приводит к увеличению вероятности атак, т.к. устройство будет взаимодействовать с внешним миром через различные сетевые соединение (Wi-Fi, Ethernet и т. д.), и поэтому потребуется дополнительные уровни безопасности.
Правило здесь очень простое — максимально уменьшите поверхность атаки:
Если необходимо взаимодействовать с внешним миром, то всегда используйте только безопасное соединение (VPN, reverse SSH tunneling, TLS, HTTPS и т. д.), для удаленных подключений лучше использовать аутентификацию с открытым ключом, и отключите вход в систему root пользователю.
В публикации не ставилась цель детально углубляться в реализации указанных технологий защиты кода и данных, самое главное заключалась в предоставление понимания технологий и концепций для снижения рисков и реализации более безопасного устройства работающего на Linux.
В сфере безопасности существует концепция многоуровневой защиты, при которой всегда необходимо реализовывать более одного уровня или типа защиты. Представьте себе, что вы король замка. Как бы вы защитили свой замок? Вы можете построить замок на вершине холма, построить высокие стены, окружить замок рвом с водой, расставить лучников на вершине стены, разместить воинов внутри замка и т. д. — это все уровни защиты. Если атакующий проходит один уровень защиты, ему/ей придется столкнуться со следующим, и так далее.
Эту же концепцию можно применить и при разработке встраиваемого устройства на Linux.
Многоуровневая схема защиты по типу «матрешки»
В конце концов, не существует 100% защищенной системы. В отличие от вас, злоумышленнику необходима только одна уязвимость, чтобы взломать устройство. Любой взлом, это вопрос времени. Поэтому защита устройств это постоянный непрерывный процесс, который требует постоянного совершенствования технологий и инструментов защиты. Но необходимо не забывать, что система должна быть «достаточно безопасной». Использование методом защиты должно быть рациональным, иначе мы столкнемся с трудностями эксплуатации систем и только понизим уровень безопасности устройства.
Пост основан на публикации Sergio Prado, Introduction to Embedded Linux Security, с изменениями и дополнениями.
Источник статьи: https://habr.com/ru/company/vdsina/blog/560664/
Концепции безопасности
Безопасность — это снижение рисков. В РФ существуют стандарты ГОСТ Р ИСО/МЭК по информационной безопасности призванные обеспечить защиту компьютерных автоматизированных систем. Но в большинстве российских стандартов в этой области, нет детальной информации как строить систему защиты встраиваемых систем. Более детализированные руководства по безопасности выпускают ведомства на коммерческой основе. Поэтому воспользуемся руководящими документами Агентства Европейского союза по кибербезопасности (European Union Agency for Cybersecurity, ENISA). Далее, описанная концепция безопасности, основана на руководстве по организации защиты встраиваемых систем — Hardware Threat Landscape and Good Practice Guide (распространяется бесплатно).
Руководство вводит следующие понятия: Владельцы (owners) — собственники использующие продукт или услугу, такие как конечный пользователь, производитель, владелец бизнеса и т.д. В свою очередь владельцы нацелены на защиту своих активов (assets). Активом является какая-либо ценность в продукте или услуге (данные, программный код, репутация, и т.д.). Антагонистом в этой схеме выступает злоумышленник (threat actors), человек или абстрактный объект (хакер, конкурент, правительство и т.д.), который создает угрозу (threat) нацеленную на актив таким образом, чтобы это могло привести к ущербу. Для реализации угрозы, злоумышленник будет исследовать уязвимости (vulnerabilities), слабые места в системе с помощью вектора атаки (attack vector), метода или пути, используемого злоумышленником для доступа или проникновения в целевую систему.
На диаграмме ниже очень представлены данные концепции:
Термины и отношения, связанные с угрозами, в соответствии с ENISA Threat Landscape 2013
В итоге это походит на игру щит и меч, между владельцем и злоумышленником. Насколько далеко зайдет владелец, чтобы защитить активы? Как далеко зайдет злоумышленник, чтобы создать угрозу? Все это зависит от стоимости активов, которая может быть выражена в виде эквивалента денежных средств, репутации, безопасной обстановке, и т.д. Восприятие ценности может быть неодинаковым для владельцев и злоумышленника.
Определение активов (и их стоимости) для снижения рисков компрометации выполняется в ходе моделирования угроз (threat modeling).
Моделирование угроз
При моделирование угроз выявляются потенциальные угрозы и расставляются по приоритету. По сути, это процесс оценки рисков, при котором происходит оценка стоимости активов и затраты на их защиту. Результатом моделирования угроз является модель угроз (threat model) вашего продукта:
Моделирование угроз
Существует различные методы и методологии, призванные помочь при моделировании угроз, включая STRIDE, DREAD, VAST, OCTAVE и многие другие.
Для общего понимания рассмотрим STRIDE и DREAD.
Модель STRIDE хорошо помогает классифицировать угрозы, была разработана в Microsoft. Название STRIDE является аббревиатурой от шести основных типов угроз:
- Spoofing: спуфинг;
- Tampering: фальсификация;
- Repudiation: отказ;
- Information disclosure: раскрытие информации;
- Denial of service and Escalation of privileges: отказ в обслуживании и повышение привилегий.
Методология STRIDE
STRIDE можно использовать для выявления всех угроз, нацеленных на активы владельца:
Таблица 1. Методология STRIDE.
Угрозы (threats) | Описание | Свойство | Пример |
Spoofing (спуфинг) | Притвориться кем-то другим | Подлинность | взломана электронная почта и от имени владельца (жертвы) почты рассылаются электронные сообщения |
Tampering (фальсификация) | Изменение данных или кода | Честность | Исполняемый файл подменен хакерами |
Repudiation (отказ) | Заявление о невыполнении определенного действия | Безотказность | «Я не отправлял электронное письмо Алисе» |
Information disclosure (раскрытие информации) | Утечка конфиденциальной информации | Конфиденциальность | Данные кредитной карты доступны в сети Интернет |
Denial of service (отказ в обслуживании) | Отсутствие доступности сервиса | Доступность | Web-приложение не отвечает на запросы пользователей |
Elevation of privileges (повышение привилегий | Возможность совершать несанкционированные действия | Авторизация | обычный пользователь может удалить учетную запись администратора |
Методология DREAD призвана оценивать риски угроз компьютерной безопасности. Название представляет собой аббревиатуру пяти категорий угроз безопасности:
- Damage — ущерб (насколько серьезной будет атака);
- Reproducibility — воспроизводимость (насколько легко воспроизвести атаку);
- Exploitability — возможность использования (какой объем работы необходимо выполнить для реализации атаки);
- Affected users — затронутые пользователи (сколько людей будет затронуто);
- Discoverability — обнаруживаемость (насколько легко обнаружить угрозу).
Таблица 2. Методология DREAD.
Критерий | Высокий (3) | Средний (2) | Низкий (1) |
Damage (ущерб) | Злоумышленник полностью может скомпрометировать систему, пройти полную авторизацию, работать с правами администратора, загрузить на хост любой код | Утечка конфиденциальной информации | Утечка тривиальной информации |
Reproducibility (воспроизводимость) | Атака может быть выполнена в любой момент, и не требует окна временного окна | Атака может быть выполнена только во время открытого окна, и время сеанса доступа к системе ограниченно | Атаку сложно воспроизвести, даже зная про наличие уязвимости в безопасности |
Exploitability (возможность использования) | Начинающий программист может совершить атаку за короткое время | Опытный программист может совершить атаку, по пошаговой инструкции | Атака требует высокой квалификации программиста и требуется глубокие знания в применение эксплойтов |
Affected users (затронутые пользователи) | Все пользователи, конфигурация по умолчанию, затронуты ключевые клиенты | Некоторые пользователи, конфигурация не по умолчанию | Небольшой процент пользователей; влияет на анонимных пользователей |
Discoverability (обнаруживаемость) | Опубликованная уязвимость основной части продукта | Уязвимость затрагивает редко используемые компоненты | Скрытая уязвимость; маловероятно, что работающие пользователи получат проблемы |
В то время как модель STRIDE помогает идентифицировать угрозы, методология DREAD помогает их ранжировать. Для каждой угрозы необходимо пройтись по категориям и выставить соответствующий бал: низкая вероятность (1 балл), средняя вероятность (2 балла), высокая вероятность (3 балла). По итогу, будет получен ранжированный список угроз и стратегий задач. Пример:
Таблица 3. Список угроз и стратегий задач
Угроза | Баллы | Решение |
Любой пользователь может получить доступ к административной панели в Web-приложении и изменить конфигурацию устройства | 14 | Реализовать механизм аутентификации пользователей |
Приложение доступное по сети может быть использовано для запуска неавторизованного кода | 13 | Удалить лишние привилегии у приложения и запустите его в контейнере |
При получение физического доступа, злоумышленник может легко извлекать пользовательские данные | 12 | Использовать шифрование для защиты пользовательских данных |
используя атаку «человек посередине» (Man in the middle, MitM), злоумышленник может изменить или подменить образ прошивки в процессе обновления | 11 | Проверять цифровую подпись образа обновления |
Злоумышленник может выполнить атаку «отказ в обслуживании» (Denial of Service, Dos) на устройство | 11 | Создать правила брандмауэра, чтобы избежания или снижения влияния DoS-атак |
... | ... | ... |
Моделирование угроз позволяет сформировать четкое понимание что необходимо защищать, каким образом планируется это защищать, и сколько будет стоить реализация защиты. Эту часть модели угроз продукта, необходимо переоценивать для каждого цикла разработки. В результате модель угроз будет содержать приоритетный список угроз, над которыми необходимо работать, так что можно сосредоточиться на реализации мер защиты для повышения безопасности продукта.
Безопасный запуск системы (Secure Boot)
Как убедиться в том, что программный код, который используется для запуска системы не подложный? Для решения этой задачи необходимо обеспечить безопасный процесс запуска системы.
Целью обеспечения безопасного запуска системы является защита целостности и аутентичности загружаемых файлов. Защита процесса загрузки обычно основана на проверке цифровых подписей. Встраиваемая система Linux обычно состоит из трех основных компонентов: загрузчика, ядра и корневой файловой системы (rootfs). Все эти компоненты подписаны, и подписи проверяются во время загрузки.
Например, для проверки подписи загрузчика (bootloader) можно использовать какой-либо аппаратный механизм, реализованный в виде отдельной микросхемы или устройства. Аппаратный механизм в первую очередь проверяет цифровую подпись ядра, затем происходит загрузка виртуального диска (ramdisk image), который в свою очередь проверяет цифровые подписи корневой файловой системы (root filesystem). Поскольку у нас есть один компонент, проверяющий цифровую подпись следующего в цепочке загрузки, этот процесс часто называют цепочкой доверия (chain-of-trust).
Пример, как реализована цепочкой доверия (chain-of-trust) на устройстве NXP iMX6
Все начинается с запуском исполняемого кода из ПЗУ (ROM) в SoC. В NXP iMX6 есть аппаратный компонент под названием High Assurance Boot (HAB), который проверяет на первом этапе цифровую подпись загрузчика (bootloader), что позволяет реализовать процесс безопасной загрузки устройства. High Assurance Boot на устройствах iMX6 так же можно назвать Root of Trust (корневое доверие), поскольку если компонент будет скомпрометирован, то весь дальнейший процесс безопасной загрузки также будет скомпрометирован.
Компоненту HAB для проверки цифровой подписи загрузчика (bootloader) необходимо сгенерировать пару ключей (открытый и закрытый). Загрузчик подписывается закрытым ключом, а внутри SoC сохраняется открытый ключ.
Внутри чипа iMX6 находится память однократной записи (One-time programmable memory) предназначенная для хранения ключей, т.е. хранится хеш открытого ключа.
Загрузчик при запуске (например, U-Boot) проверяет подпись ядра Linux. Для этого обычно используется формат образа, называемый FIT image. FIT image представляет собой контейнер для бинарных файлов с поддержкой хеширования и цифровой подписи, и обычно содержит образ ядра Linux, файлы дерева устройств и стартовый ramdisk. После создания пары ключей, необходимо подписать бинарные файлы в FIT image с помощью закрытого ключа, и настроить U-Boot для использования открытого ключа для проверки подписи FIT image.
После загрузки ядра Linux запустится программа инициализации из образа ramdisk. ramdisk содержит логику для окончательной проверки целостности корневой файловой системы перед ее монтированием. Существует несколько вариантов реализации данной задачи. Один из наиболее распространенных вариантов — использование модуля ядра device-mapper verity (dm-verity). Модуль ядра dm-verity обеспечивает проверку целостности блочных устройств и требует наличия rootfs только для чтения. Другой вариант заключается в использование IMA или dm-integrity, если необходимо монтировать корневую файловую систему в режиме чтение-запись.
Схема полного процесса безопасной загрузки.
Это только один пример реализации безопасной загрузки, так же данный подход может быть применен к другим платам и ARM SoC.
При реализации защиты необходимо помнить что 100% безопасности не существует.
17 июля 2017 г. была публично раскрыта уязвимость в ROM используемой для безопасной загрузке в нескольких устройств NXP (семейства i.MX6, i.MX50, i.MX53, i.MX7, i.MX28 и Vybrid). И если ваша цепочка доверия скомпрометирована, то все скомпрометировано! Поэтому, после реализации системы защиты необходимо в течение всего срока эксплуатации устройства, следить за публикациями уязвимостей.
Хотя безопасная загрузка обеспечивает аутентичность и целостность исполняемого кода, она не защищает устройство от считывания исполняемого кода/данных злоумышленником. Поэтому, для защиты своей интеллектуальной собственности или обеспечения конфиденциальность данных, необходимо использовать шифрование.
Исполняемый код и шифрование данных
Для защиты конфиденциальных данных системы и пользователей необходимо обеспечить шифрование на самом устройстве. Данные — это любая информация, созданная во время работы устройства, включая базы данных, файлы конфигурации и так далее.
Не всегда требуется реализовывать шифрования исполняемых файлов, а полное шифрование корневой файловой системы встречается очень редко. Большинство используемых компонентов — это бесплатное программное обеспечение с открытым исходным кодом, поэтому его не имеет смысла скрывать. Существует также проблема в лицензирование GPLv3 и Tivoization (использование любого программного обеспечения GPLv3 вынудит вас предоставить пользователю механизм для обновления программного обеспечения, и это усложнит задачу, если шифруете исполняемый код). Наиболее распространенный подход — шифрование только приложений, которые вы разработали для устройств. Обычно это ваша интеллектуальная собственность.
В Linux доступно два сценария использования шифрования данных: полное шифрование диска (full disk encryption) и пофайловое шифрование (file-based encryption).
Полное шифрование диска работает на уровне шифрование блоков, при этом зашифровывается весь диск или его раздел, для этого необходимо использовать dm-crypt.
Пофайловое шифрование работает на уровне файловой системы, где каждый каталог/файл может быть зашифрован отдельным ключом. Две наиболее распространенные реализации файлового шифрования — это fscrypt и eCryptFS. fscrypt — это API, доступное в файловых системах, таких как: EXT4, UBIFS и F2FS. eCryptFS — более общее решение, реализованное в виде дополнительного слоя, который накладывается поверх существующей файловой системы и не зависит от нее.
Шифрование это хорошо, но как быть с ключами, где и как их хранить?
Ключи шифрования
Поскольку алгоритм с асимметричным ключом слишком медленный при использования для шифрования диска или файлов, то используется алгоритм с симметричным ключом. Это означает, что для шифрования и дешифрования используется один и тот же ключ. Причем этот ключ должен быть расположен где-то в файловой системе, чтобы можно было расшифровать и зашифрованный исполняемый код/данные.
Но оставлять ключ в файловой системе нельзя, верно?
В истории отметились некоторые случаи, когда компании проигнорировали этот момент и получили в дальнейшем проблемы. Например, для первой игровой консоли Xbox, Эндрю «bunnie» Хуанг, исследователь по безопасности, разработал устройство на базе FPGA для прослушивания коммуникационной шины и извлечения ключей шифрования из нее. Ключи были легко получены только потому, что они передавались в виде открытого текста, без какой-либо защиты. По сути Эндрю сделал снифер и сделал дамп всех передаваемых данных. Кстати, Эндрю Хуанг является автором книги по взлому оборудования под названием «Взлом Xbox».
Подключение снифера к материнской плате Xbox
Защита зашифрованного исполняемого кода/данных так же надежна, как и защита ключа для их расшифровки! Итак, одна из проблем, с которыми сталкивается шифрование, — это хранение ключей.
Подходы к хранению ключей
Для настольных компьютеров или смартфонов, использующие шифрование файловой системы, ключ может быть получен из пароля пользователя (парольной фразы).
Во встраиваемых системах обычно нет интерактивного взаимодействия с пользователем для получения ключа из пароля каждый раз, когда происходит загрузка устройства. Таким образом, ключ должен храниться в зашифрованном виде в файловой системе или в безопасном хранилище.
Например, процессоры NXP i.MX содержат уникальный главный ключ (предварительно записанный NXP), доступ к которому может получить только специальный аппаратный модуль — CAAM (Cryptographic Accelerator and Assurance Module). Данный модуль используется для шифрования ключа и сохранения его в файловой системе. Во время загрузки устройства, модуль CAMM используется для расшифровки ключа и восстановления простого ключа, который будет использоваться для расшифровки файловой системы. Поскольку ключ внутри модуля CAAM недоступен, зашифрованный ключ защищен.
Если в процессор не интегрированы подобный функции безопасности, то можно воспользоваться внешними аппаратными модулями такими как Secure Element и TPM.
Secure Element — это безопасная компьютерная система, в состав которой входит безопасное хранилище с собственными защищенными приложениями (обычно реализуется с помощью Java Card). Что предоставляет возможность реализации различных алгоритмов шифрования, но в большинстве случаев интегрируют Стандарт шифрования с открытым ключом 11 (PKCS # 11). Secure Elements используется в смарт-картах и SIM-картах.
Доверенный платформенный модуль, или TPM (trusted platform module) – это отдельный микрочип на системной плате компьютера, разработанный согласно спецификации ISO/IEC 11889, и выполняет специфический круг задач, связанных с криптографией и защитой компьютера. TPM обеспечивает безопасное хранение данных, поэтому его можно использовать для хранения главного ключа, который в дальнейшем будет использоваться для шифрования/дешифрования ключа шифрования файловой системы. Помимо защищенного хранилища подобные устройства также предоставляют дополнительные функций безопасности, таких как генерация случайных чисел, вычисление хэшей, функции шифрования и подписи и т. Д. Так же TPM может быть реализован программно, но большинство реализации выполнено в виде аппаратного модуля.
Компания STMicroelectronics разработала модуль TPM STPM4RasPI для Raspberry Pi устройств. Данный модуль подключается на 40-контактный разъем и может работать по интерфейсу: I²C или SPI. Краткое руководство — Raspberry Pi extension board with an ST33 Trusted Platform Module.
TPM STPM4RasPI от STMicroelectronics
Модуль STPM4RasPI подключен к Raspberry Pi
После подключения модуля по интерфейсу SPI и установке драйверов, модуль STPM4RasPI обнаруживается в системе:
STPM4RasPI подключен к Raspberry Pi
Третьим вариантом обеспечения безопасного хранилища данных является использование доверенной среды исполнения (Trusted Execution Environment, TEE). TEE — среда для исполнения кода и хранения данных, которая надежно изолирована от основной ОС устройства. Существую различные варианты реализации TEE:
- Использовать TrustZone в процессорах ARM — разделение TEE и основной ОС в рамках одного ядра процессора;
- Запуск TEE на отдельном ядре в рамках SoC и общение с ней через аппаратный интерфейс;
- Запускать TEE на отдельном SoC и установить с ним защищенное соединение через внешний интерфейс, например, SPI или SMbus.
Многие устройства используемые в повседневной жизни используют надежную среду исполнения, включая смартфоны, телевизионные приставки, игровые консоли и Smart TV.
Если вы реализуете в своем встраиваемом устройстве шифрование, то вам придется подумать о хранении ключей и управлении ими с самого начала проекта.
Многоуровневая безопасность
Использование безопасной загрузки и шифрования данных недостаточно для комплексной защиты встраиваемых устройств на Linux. Система защиты должна быть многоуровневая, где на каждом уровне используются свои методы защиты данных. Сочетание нескольких уровней защиты затрудняет взлом устройства злоумышленником. Например, можно защитить исполняемый код и обеспечить шифрование данных, но если выше приложение работает с ошибками, которые могут быть использованы, то у вас будет проблемы с безопасностью. Если на приложение можно осуществить различные векторы атак (пользовательский ввод, файлы конфигурации, сетевые соединения и т. д.), то все предыдущие используемые методы защиты данных не помогут.
Безопасный код
Если приложение подвержено векторам атак (пользовательский ввод, файлы конфигурации, сетевое взаимодействие и т. д.), то эти ошибки могут использовать эксплойты. В частности, программы написанные на небезопасных языках программирования таких как C/C++, применимы такие атаки как: переполнение буфера, разбиение стека и форматирование строк.
Как пример, в ядре Linux (с версии 2.6.34 до 5.2.x) была обнаружена ошибка переполнения буфера, при которой функциональность vhost переводила буферы виртуальной очереди в IOV. Проблема позволяет из гостевой системы создать условия для переполнения буфера в модуле ядра vhost-net (сетевой бэкенд для virtio), выполняемом на стороне хост-окружения. Атака может быть проведена злоумышленником, имеющим привилегированный доступ в гостевой системе, во время выполнения операции миграции виртуальных машин.
Фрагмент кода с vhost
Уязвимости был присвоен номер CVE-2019-14835 и исправлена в 2019 году. На практике пользователь виртуальной (гостевой) машины мог использовать эту уязвимость для получения root доступа на основной хост-машине. Уязвимость (и многие другие) присутствовала в ядре Linux в течение нескольких лет!
В конце концов, в программном обеспечении всегда будут ошибки, но мы можем попытаться свести их к минимуму. И для этого мы можем использовать инструменты статического анализа.
Инструменты статистического анализа кода
Инструменты статического анализа позволяют анализировать исходный код (без запуска программы), для нахождения проблем до того, как вы столкнетесь с ними во время исполнения. Эти инструменты обнаруживают программные ошибки, такие как утечки памяти, целочисленное переполнение, выход за пределы массива, и многое другое!
Существует множество различных инструментов с открытым исходным кодом (cppcheck, splint, clang и т. д.). И коммерческие варианты, такие как популярный PVS-Studio. Компиляторы обычно содержат встроенный инструмент статического анализа, который во время компиляции выдает предупреждения или ошибки. Вот почему мы никогда не должны игнорировать предупреждения компилятора.
Итак, первый шаг к минимизации риска потенциальных уязвимостей в приложениях — никогда не игнорировать предупреждения компилятора и использовать инструменты статического анализа. Но некоторые проблемы очень сложно, а иногда и невозможно выявить на уровне исходного кода. Вот почему нам может потребоваться добавить защиту приложения по время его выполнения.
Защита приложения во время работы
Защита во время выполнения препятствует динамическому анализу приложения. Целью злоумышленника является адресное пространство в памяти используемой приложением. Например, злоумышленник может из памяти приложения считать ключи и пароли в открытом виде, или изменить некоторые данные. Многие из нас еще в детстве уже были хакерами и изменяли память процесса с помощью программы ArtMoney. Путём внедрения в процесс или в файл игры, ArtMoney обнаруживает ячейки адресов памяти, которые отвечают за хранение пользовательских настроек игры: время (таймер), количество денег, силы, жизней, патронов и т. д. Таким образом, можно было бесплатно себе обеспечить бесконечные патроны, максимум игровой валюты, или бесконечную жизнь.
Для защиты доступа к адресному пространству приложения используют подобные методы и инструменты:
- AddressSanitizer (ASan) — инструмент, созданный исследователями по безопасности от Google, для выявления проблем доступа к памяти в программах C и C ++. Когда исходный код приложения C/C ++ компилируется с включенной опцией AddressSanitizer, программа будет оснащена инструментарием во время выполнения для выявления и сообщения об ошибках доступа к памяти.
- ASLR (рандомизация адресного пространства) — метод компьютерной безопасности, который случайным образом упорядочивает позиции адресного пространства ключевых областей данных процесса (текст, стек, куча, библиотеки и т. Д.). Данный режим включается на уровне ядра Linux с последующей перекомпиляцией.
- Valgrind — еще один очень полезный инструмент, предназначенный обнаружения утечек памяти, а также профилирования.
Использование средств защиты это всегда выбор компромиссов, например использование ASLR снижает общую производительность системы, другие инструменты осложняют отладку и диагностику приложения.
Инструменты для фаззинга
Фаззинг — это техника автоматизированного тестирования, при которой на вход программе подаются специально подготовленные данные, которые могут привести её к аварийному состоянию или неопределённому поведению. Для проведения фаззинга существует множество бесплатных инструментов с открытым исходным кодом, такие как AFL и syzkaller (фаззер ядра Linux). Данные программы многие исследователи по безопасности и злоумышленники, используют для поиска уязвимых мест с точки зрения безопасности в программном обеспечении. В некоторых случаях исследователи разрабатывают свои инструменты для фаззинга, и это позволяет им зарабатывать большие деньги на платформах BugBounty, обнаруживая ошибки в программном обеспечении.
Такие методы и инструменты как фаззинг, статический анализ и защита во время выполнения, помогут значительно уменьшить количество ошибок в программном обеспечении. Но это не значит, что вы можете на этом остановится! В программном обеспечении всегда есть и будут ошибки, поэтому нужен еще один уровень защиты на случай, если программа будет взломана. И это приводит нас к системе разграничения прав доступа.
Разрешения
Один из способов уменьшить ущерб при эксплуатации уязвимости — не запускать программы с привилегиями root (суперпользователя)! Вы должны воспользоваться системой разграничения прав доступа и запускать процессы от имени непривилегированных пользователей, которые должны быть добавлены в группы с доступом только к тем ресурсам, которые необходимы для запуска.
Это называется принципом набора минимальных привилегий и является одним из правил разработки безопасных систем. Приложения должны запускаться только с теми привилегиями, которые необходимы для выполнения своей работы.
Но проблема заключается в том, что в Linux для выполнения некоторых привилегированных операций (например, настройка времени, использование RAW сокетов и т. д.) требуются права «root пользователя». В этом случае потребуется запустить программу от имени root, но это неправильный подход. Один из способов решить эту проблему использовать гранулированные права доступа, в Linux это называется — capabilities.
Гранулированные права доступа (Linux capabilities)
Гранулированные права доступа (Linux capabilities) — это детализированная система контроля доступа для процессов, выполняемых с привилегиями root.
Ядро Linux делит привилегии связанные с суперпользователем на отдельные единицы, известные как capabilities, которые могут быть независимо включены или отключены. Идея состоит в том, что программа будет работать от имени пользователя root, но при этом будет включать только те capabilities, которые необходимы для выполнения своей работы. В Linux для перечисления capabilities, которые требуются для запуска конкретной программе, используется инструмент — getcap:
$ getcap /usr/bin/ping
/usr/bin/ping = cap_net_raw+ep
Предоставление процессу определенных гранулированных прав от пользователя root, может быть весьма избыточным, поэтому следует еще больше минимизировать набор выдаваемых прав с на основе мандатного управления доступом.
Мандатное управление доступом
Linux традиционно поддерживает Дискреционное управление доступом (Discretionary Access Control, DAC). DAC — управление доступом субъектов к объектам на основе списков управления доступом или матрицы доступа. Субъект с определенным правом доступа может передать это право любому другому субъекту. Например, когда вы расписываете доступ к файлу, вы указываете: имя владельца файла (субъект), права на чтение, права на запись, права на запуск, для субъектов: пользователь или программа выполняющаяся под именем пользователя. Объектами права выступают: файлы, каталоги, и т.д.
Дискреционное управление доступом
Пример дискреционного управления доступом к файлам в Linux Ubuntu:
root@bananapim64:/# ls -l
total 72
drwxr-xr-x 2 root root 4096 May 12 22:11 bin
drwxr-xr-x 3 root root 4096 Jun 2 16:53 boot
drwxr-xr-x 16 root root 3660 May 30 01:08 dev
drwxr-xr-x 94 root root 4096 May 30 01:08 etc
drwxr-xr-x 3 root root 4096 Feb 4 11:33 home
drwxr-xr-x 16 root root 4096 May 13 21:36 lib
drwx------ 2 root root 16384 Feb 4 11:33 lost+found
...
Другой тип контроля доступа называется Мандатное управление доступом (Mandatory access control, MAC). MAC — разграничение доступа субъектов к объектам, основанное на назначении метки (мандата) конфиденциальности для информации, содержащейся в объектах, и выдаче официальных разрешений (допуска) субъектам на обращение к информации такого уровня конфиденциальности.
Мандатное управление доступом
Модель MAC реализована в ядре Linux на основе фреймворка — Linux Security Module (LSM). LSM — структура, которая позволяет ядру Linux поддерживать различные модели компьютерной безопасности. Двумя наиболее известными модулями безопасности в Linux, реализующими MAC, являются SELinux и AppArmor:
- SELinux — одна из самых популярных (и сложных) реализаций MAC, первоначально была разработана NSA, сегодня используется в Android и Fedora.
- AppArmor — также является популярной и более дружественной реализацией MAC, поддерживается фондом Canonical и используемой в дистрибутивах Linux, таких как Ubuntu и Debian.
Но иногда использование MAC недостаточно для защиты системы от уязвимого приложения. В этом случае обеспечить должную защиту поможет запуск приложения в песочнице.
Песочница для приложений
Песочница позволяет изолировать приложения от основной системы. Самый старый механизм реализации песочницы в Linux это — chroot. Но chroot позволяет изолировать только файловую систему, что не обеспечивает полную изоляцию приложения от системы. Можно использовать аппаратную виртуализацию для каждого приложения, но этот подход очень требователем к аппаратным ресурсам и не подходит для встраиваемых система. В настоящее время существует два решения для запуска приложений в песочнице, — это контейнеры и Trusted Execution Environments (TEE).
Контейнеры
Контейнер Linux — представлен в виде минимальной файловой системы, содержащей только необходимые программные компоненты для запуска определенного приложения или группы приложений. Контейнер работает полностью изолированно от основной системы, и работает только на ядре Linux.
Технология контейнеризации приложения в Linux базируется на функция ядра Linux, таких как:
- Пространство имён (namespaces): позволяет изолировать и виртуализировать глобальные системные ресурсы множества процессов. Примеры ресурсов, которые можно виртуализировать: ID процессов, имена хостов, ID пользователей, доступ к сетям, межпроцессное взаимодействие и файловые системы;
- Контрольная группа (cgroups): позволяет разделять системные ресурсы (ЦП, память, ввод-вывод) по процессам или группам процессов;
- Seccomp (secure computing mode): позволяет ограничить системные вызовы, которые может выполнять процесс. Если злоумышленник получит возможность выполнить произвольный код, seccomp не даст ему использовать системные вызовы, которые не были заранее объявлены.
Для работы с контейнерами в Linux существуют менеджеры облегчая создание, эксплуатацию, изменение контейнеров, такие как: LXC, Systemd-nspawn, Podman и самый популярный Docker.
Использование контейнерезации приложений сам по себе не является гарантией защиты системы, требуется правильно ограничить разрешения каждого процесса внутри контейнера и контролировать обмен данными между ними, уменьшая вектор атак и повышая безопасность системы.
В случае использования Docker при создания файла сборки образа — Dockerfile, необходимо воспользоваться лучшими практиками — Best practices for writing Dockerfiles. Интересный проект Dockle позволяет выполнить статистический анализ файла Dockerfile и получить отчет о проблемных местах. Запускать Dockle можно путем использование утилиты, или из контейнера.
Полученный отчет безопасности от Dockle
Помимо Dockle существуют и другие инструменты для статистического анализа, более подробно можно ознакомится в публикации — 10+ top open-source tools for Docker security.
Комбинируя использование контейнеров вместе с модулем безопасности (например, AppArmor или SELinux), можно существенно повысить защиту системы.
В системе основанной на контейнерах, если ядро ОС будет скомпрометировано, то и вся операционная система окажется под угрозой. В этом случае доверенная среда выполнения — это еще один уровень безопасности, который может это предотвратить.
Доверенная среда выполнения
Доверенная среда выполнения (Trusted Execution Environment, TEE) — это среда, изолированная и защищенная с точки зрения конфиденциальности (никто не имеет доступа к данным) и целостности (никто не может изменить код) в которой исполняется код и хранятся данные.
В системе с доверенная среда выполнения (TEE) приложения делятся на:
- Ненадежные приложения (untrusted applications, UA), работающие в Rich Execution Environment (REE);
- Доверенные приложения (trusted applications, TA), работающие в Доверенной среде выполнения.
Только доверенные приложения, работающие в TEE (Secure World), имеют полный доступ к основным аппаратным ресурсам, периферийным устройствам и памяти. Аппаратная изоляция защищает доверенные приложения (ТА) от ненадежных приложений, работающих в основной операционной системе (Non-Secure World).
Схема работы системы вместе с доверенной средой выполнения
Для использование доверенной среды выполнения (TEE) необходима поддержка на уровне аппаратного обеспечения, для возможности разделения и изолирования оборудования (шины, периферийные устройства, области памяти, прерывания и т. д.), чтобы предотвратить доступ ненадежных приложений к защищенным ресурсам. Эта функция уже присутствует в большинстве современных процессоров (например, TrustZone ARM, MultiZone RISC-V, Intel SGX).
Доверенная среда выполнения используют многие устройства, такие как: смартфоны, телевизионные приставки, игровые консоли и Smart TV. Существуют коммерческие реализаций TEE: Kinibi, QSEE и iTrustee. И реализации с открытым исходным кодом: Trusty и OP-TEE. TEE может быть хорошим решением для приложений-песочниц, хранения и управления ключами шифрования, хранения учетных данных и конфиденциальных данных и управления ими, а также защиты информации, защищенной авторским правом.
Несмотря на все меры, которые были рассмотрены выше, операционная система с миллионами строк кода обязательно будет содержать ошибки и уязвимости! А наличие системы обновлений очень важно для встраиваемых систем и подключенных устройств, где безопасность является ключевой особенностью продукта.
Система обновлений
Система обновлений должна быть спроектирована на ранних этапах разработки продукта, по возможности с поддержкой OTA (обновление по беспроводным каналам связи, Over-the-Air). Использование системы обновления создает для некоторые проблемы для разработки продукта, включая безопасность протокола связи, атомарность процесса обновления, защиту от сбоев питания, использование полосы пропускания и хранилища, возможности отката и т. д.
Реализовать систему обновление можно в соответствие с основными стратегиями, такими как:
- На основе приложений: легко реализовать, но как насчет остальной части операционной системы?
- На основе пакетов: образы обновлений небольшие, но обновления не атомарны, и зависимости пакетов могут создать дополнительные проблемы;
- На основе образа: использование механизма A/B — очень хорошее решение, но загрузка целиком всего образа может быть проблемой в случаях низкой пропускной способности Интернет-канала, а так же потребуется в два раза больше пространства в хранилище для сохранение нового образа;
- На основе контейнера: одна из лучших стратегий. Размер нового контейнера будет существенно меньше чем обновление целиком из образа. Обновление является атомарным, отказоустойчивым, использует меньшую полосу пропускания, и процесс обновления проходит быстрее с минимальным временем простоя и возможностью отката.
Необходимо помнить что использование OTA-обновлений приводит к увеличению вероятности атак, т.к. устройство будет взаимодействовать с внешним миром через различные сетевые соединение (Wi-Fi, Ethernet и т. д.), и поэтому потребуется дополнительные уровни безопасности.
Сетевая безопасность
Правило здесь очень простое — максимально уменьшите поверхность атаки:
- Закрыть все неиспользуемые порты TCP/UDP
- Отключите все неиспользуемые протоколы (например, IPv6, PPP и т. д.)
- Настройте правила брандмауэра для предотвращения входящих/исходящих соединений
- Защитите от DoS атаки
- Предотвращение сканирования портов и т. д.
Если необходимо взаимодействовать с внешним миром, то всегда используйте только безопасное соединение (VPN, reverse SSH tunneling, TLS, HTTPS и т. д.), для удаленных подключений лучше использовать аутентификацию с открытым ключом, и отключите вход в систему root пользователю.
Заключение
В публикации не ставилась цель детально углубляться в реализации указанных технологий защиты кода и данных, самое главное заключалась в предоставление понимания технологий и концепций для снижения рисков и реализации более безопасного устройства работающего на Linux.
В сфере безопасности существует концепция многоуровневой защиты, при которой всегда необходимо реализовывать более одного уровня или типа защиты. Представьте себе, что вы король замка. Как бы вы защитили свой замок? Вы можете построить замок на вершине холма, построить высокие стены, окружить замок рвом с водой, расставить лучников на вершине стены, разместить воинов внутри замка и т. д. — это все уровни защиты. Если атакующий проходит один уровень защиты, ему/ей придется столкнуться со следующим, и так далее.
Эту же концепцию можно применить и при разработке встраиваемого устройства на Linux.
Многоуровневая схема защиты по типу «матрешки»
В конце концов, не существует 100% защищенной системы. В отличие от вас, злоумышленнику необходима только одна уязвимость, чтобы взломать устройство. Любой взлом, это вопрос времени. Поэтому защита устройств это постоянный непрерывный процесс, который требует постоянного совершенствования технологий и инструментов защиты. Но необходимо не забывать, что система должна быть «достаточно безопасной». Использование методом защиты должно быть рациональным, иначе мы столкнемся с трудностями эксплуатации систем и только понизим уровень безопасности устройства.
Пост основан на публикации Sergio Prado, Introduction to Embedded Linux Security, с изменениями и дополнениями.
Источник статьи: https://habr.com/ru/company/vdsina/blog/560664/