Защита систем. Чему «Звездные войны» учат инженера ПО — страница 39 из 68

Разрешения

Традиционные разрешения можно использовать для защиты файлов и других объектов от других участников. Тем не менее у разрешений есть проблемы с выразительностью, и, как пишет Марк Миллер: «На практике программисты контролируют доступ частично путем манипулирования графом доступа и частично путем написания программ, поведение которых ослабляет полномочия, проходящие через них». (Мы углубимся в работу д-ра Миллера о полномочиях попозже в этой главе.) Понять взаимодействие разрешений было трудно даже тогда, когда системы были проще, и Cops (очень ранний инструмент проверки конфигурации системы) включал в себя инструмент построения графов, который просматривал все разрешения в поисках способов использования их уязвимости.

Способности

Способность – это специфический программный шаблон, и довольно изящный. Он сочетает в себе две вещи, о которых часто думают отдельно: идентичность и доступ. Если у вас есть такая способность, у вас есть и то и другое. Способность, эквивалентная (read, /etc/hosts), может быть 789432. Вы вызываете open(789432), и вы можете читать файлы в каталоге hosts. Аналогично, (write, /etc/hosts) может быть 723190. Они похожи на дескрипторы файлов или безопасные указатели.

Конечно, числа должны быть намного длиннее, чтобы их нельзя было угадать, или, в некоторых реализациях, в них включается код аутентификации сообщения, чтобы угадывание было легко отловить. Таким образом, предоставление программе способности явно предоставляет полномочия, связанные с этой способностью. Поскольку числа не поддаются угадыванию, программы могут использовать только те способности, которые им предоставлены. Это отличается от установки разрешений участнику и приводит к гораздо более строгому ограничению доступа.

Термин «способность» (capability) – это изящное название, потому что оно означает и объект программного обеспечения, и качество, способность что-то делать. В этом подразделе я выделял курсивом способность для обозначения конкретного программного объекта в надежде не показаться слишком умным.

К сожалению, у нас нет императора, который мог бы поражать молниями людей, переопределяющих термины, и способность (capability) имеет другое, несовместимое значение. Согласно странице справочника man для capabilities: «Начиная с ядра 2.2, Linux разделяет привилегии, традиционно связанные с суперпользователем, на отдельные блоки, известные как способности, которые могут быть включены и отключены независимо».

Инструменты изоляции

Инструменты изоляции обеспечивают свойство, называемое невмешательством. Точное значение: что бы ни делал разработчик, его код не может вмешиваться в чужой код. Это не дополнительное свойство, и его обеспечивают средства изоляции. Файлы не могут быть открыты; из памяти нельзя читать или писать в нее. Изоляция также означает, что злоумышленник, чей код перехватывает добросовестно запущенный код, также не может вмешаться. Это было важной целью при встраивании функциональности разделения времени в ранние компьютеры, и это также чрезвычайно полезно для безопасности. Существует широкий спектр инструментов изоляции, начиная от учетных записей пользователей и песочниц в этих учетных записях и заканчивая идентификацией приложений на мобильных телефонах и виртуальными частными облаками.

Самый лучший способ изолировать свой код от другого кода – удалить этот другой код. Код, который не выполняется, не может быть использован для уязвимости; код, который отсутствует, не может быть запущен. Сокращение постороннего кода не только делает систему более безопасной, но и ускоряет работу, поскольку этот посторонний код больше не использует цикл процессора, кусочек памяти или сеть.

Конечно, средства изоляции могут быть глючными, сложными в настройке или настроенными слишком открыто. Вы также можете уменьшить их мощность, например, запуская от имени root по умолчанию. Создатели настольных компьютеров и мобильных платформ делают подобное невозможным, но в Linux или в интернете вешей вы можете отключить безопасность.

Песочницы предназначены для ограничения того, что может происходить в учетной записи. Распространенным способом взлома Unix-сервера было использование демона прослушивания сети (долго работающей программы). На этих серверах было много корневых программ setuid. Злоумышленник, взломавший систему и использовавший эксплойт для программы setuid, мог расширить свои полномочия. Модель разрешений Unix не позволяет легко приказать: «Не может исполняться членом группы демонов», – не говоря уже о том, что «не может исполняться членом группы демонов или членом группы qmaild».

Это привело к созданию песочницы, получившей название chroot. Она давала программе ограниченную файловую систему с только известными зависимостями. К сожалению, она была сложна в использовании и из нее было относительно легко выбраться. Более современные песочницы, в том числе AppArmor, sandboxd и AppContainer, добавляют уровень ограничений, которые окружают и защищают операционную систему и другие учетные записи от демона. (Название песочница подразумевало, что можно безопасно позволить нападающему поиграть там, и оно, к сожалению, прижилось.)

Контейнеры, такие как Docker, представляют собой еще один набор границ, предназначенных для предотвращения проникновения чего-либо внутри контейнера за его пределы. Аналогично, виртуальная машина должна быть изолирована от гипервизора и другого кода. Во многих случаях это раздражает, поэтому мы используем конфигурацию, чтобы уменьшить изоляцию. Например, невозможность копирования и вставки между хост операционной системой и виртуальной гостевой операционной системой приводит к появлению таких инструментов, как VMware Tools, которые уменьшают изоляцию в пользу удобства использования.

Код как барьер

После того как вы спроектировали свою систему так, чтобы она работала с минимальными полномочиями, и разделили необходимые ей полномочия между различными подкомпонентами, появляется много возможностей для того, чтобы код действовал как защитный барьер. К ним относятся ослабление и осторожная передача обслуживания между программами (handoff). Также очень важно тщательно анализировать входные данные. Это настолько важно, что этой теме посвящена целая глава 8 «Распознавание и порча».

Ослабление

Ослаблять – значит уменьшать силу или эффект чего-либо. Программа может ослабить полномочия, решив не предоставлять их своим клиентам. Например, обычная оболочка Unix позволяет пользователю запускать любую исполняемую программу с соглашением, прописанным в./mycode. Мы могли бы создать оболочку, которая разрешает выполнение только программ, расположенных в системных каталогах, и не позволяет пользователю задавать путь. (Оболочка bash может быть вызвана как rbash, чтобы добиться это.)

Точно так же у нас есть разные способы разработки API, который принимает команды. Мы могли бы принимать все команды от нашего контрагента, скажем, ping 1.2.3.4. Мы также можем принять список команд или даже указатели на команды. Мы могли бы сделать это с помощью таблицы отображений, таких как command1:netstat, command2:ping и других, которые мы ожидаем. Удаленный вызывающий абонент тогда отправит command2 1.2.3.4. Эта последняя схема сводит к минимуму вероятность ошибок, но многие риски, связанные с синтаксическим анализом, остаются и будут рассмотрены в главе 8.

Многие интерпретаторы принимают в качестве аргумента скрипт, например python mycode.py. Мы могли бы создать версию Python, которая делает меньше и требует, чтобы весь код находился только в разрешенном каталоге, например /usr/local/python/site/. Каждый из этих способов ослабляет полномочия, предоставленные вызывающему абоненту, различными способами, настроенными на функциональность, которую они предлагают.

Программа sudo предназначена для того, чтобы вызывающие ее объекты могли запускать код с привилегиями root. Сама sudo может выполнять что угодно, но замысел состоит в ослаблении этих полномочий, чтобы только указанные пользователи могли выполнять заданные команды. Это оказывается непростой задачей отчасти потому, что sudo должна анализировать не только команду ввода, но и файл политики, который объявляет, кто и что может делать. Этот язык политики написан на умеренно сложном языке, что позволяет администраторам задавать широкий спектр допустимых команд.

Защита для представителей

Легко сказать: «Не создавайте представителя, которого можно сбить с толку!» Сложнее сделать, и еще сложнее, когда ваш код уже находится в рабочей среде.

Так что создать представителя, которого нельзя сбить с толку, непросто. На самом деле, sudo, программа, существующая почти исключительно для этого, имела долгую историю проблем с безопасностью (не все из которых были проблемами «сбитый с толку представитель»). Путаницу на уровне программиста, системы или пользователя труднее всего предотвратить, когда абстракции кажутся тонкими слоями, сквозь которые мы можем видеть.

Первый шаг – осознайте, что ваш код является представителем. Это легко понять в случае демонов, setuid-кода и кода, который обрабатывает сложные файловые структуры. Это менее очевидно в некоторых возникающих облачных шаблонах, особенно в связи с тем, что мы наследуем дорогостоящие технические неполадки, интегрируя библиотеки, которые мы не до конца понимаем. Когда вы знаете, что ваш код является представителем, встройте эту функциональность в небольшое, удобное для изоляции подмножество, о котором легко рассуждать.

Второй шаг – внимательно отнеситесь к настройке. Если вы берете конфигурацию из файла, управляемого пользователем, вы должны либо ограничить эту конфигурацию для этого пользователя, либо ограничить любое выполнение только этим пользователем. Если ваш код выполняет вызовы API, использующие ваши полномочия, и эти API не указывают пользователя в качестве параметра, это, скорее всего, приведет к путанице.