Изучаем политики systrace
Одной из полезных функций NetBSD и OpenBSD является systrace — менеджер доступа к системным вызовам. С его помощью системный администратор может задать, какими программами и как могут делаться системные вызовы. Грамотное использование systrace может существенно снизить риски, присущие запуску неграмотно написанных или нестабильно работающих программ. Политики systrace могут задавать ограничения пользователям совершенно независимо от разрешений Unix. Вы даже можете задать ошибку, которую возвратит системный вызов в случае запрета доступа, для того чтобы программа завершилась в более правильной форме. Правильное использование systrace требует от пользователя понимания системных вызовов, необходимых вещей для правильной работы программ, и как эти вещи взаимодействуют с безопасностью.
Для начала разберемся, что же такое системные вызовы? Многие администраторы были замечены за употреблением этого термина, но, к сожалению, не все они понимают в полной мере, что он означает.
Системный вызов — это функция, позволяющая вам обмениваться информацией с ядром операционной системы.
Если вы хотите получить под что-либо память, открыть TCP-порт или произвести дисковую операцию чтения/записи, то это и будет системным вызовом. Некоторые системные вызовы описаны во второй части этой статьи.
Unix также поддерживает большое количество вызовов библиотек С. Часто их путают с системными вызовами, но на самом деле это всего лишь стандартизованные куски кода, выполняющие некоторые часто используемые действия. Например, вы легко можете сами написать функцию, вычисляющую квадратные корни, но для того, чтобы, например, выделить под что-либо память, вам обязательно потребуется системный вызов. Если вы сомневаетесь, является ли какая-либо функция системным вызовом или функцией библиотеки С, воспользуйтесь онлайновым руководством.
Возможно, вы столкнетесь с какой-либо редкой функцией, не документированной в онлайновом руководстве, например, break(). Чтобы идентифицировать этот класс, вам придется покопаться в других источниках (break() является очень старым системным вызовом, используемым совместно с libdc, а не программистами. Скорее всего, поэтому его пропустили при написании man-страниц для людей).
Systrace запрещает все явно не разрешенные действия, и ведет логи запрещений в syslog. Если в программе, запущенной под systrace, возникает проблема, вы можете найти, какой системный вызов необходим программе, и решить, добавлять его в политику, переконфигурировать программу, или жить дальше с этой ошибкой.
Systrace имеет несколько важных частей: политики, инструмент создания политик, инструмент настройки доступа во время рабочего цикла и интерфейс реального времени для администратора.
чтение политик systrace
Руководство sysrtace включает полное описание синтаксиса, используемого для описания политик, но я считаю, что будет гораздо проще и понятнее разобрать пример работающей политики, а уже потом подробней остановиться на синтаксисе.
Давайте посмотрим на политику, предоставляемую OpenBSD 3.2 для named.
Перед ознакомлением с политикой для named, давайте рассмотрим некоторые общеизвестные факты о требованиях доступа к DNS-серверу. Трансферы зон осуществляются через порт 53/TCP, в то время как основные запросы обслуживаются на 53-м UDP-порту. OpenBSD по умолчанию делает chroot для named в /var/named и ведет все логи в /var/log/messages. Следовательно, можно ожидать, что системным вызовам понадобится доступ туда.
Каждый файл политик systrace находится в файле, имеющем название, совпадающее с полным путем к программе, за исключением того, что все слэши в нем заменены на подчеркивания. Файл политики usr_sbin_named содержит несколько записей разрешения доступа. Файл начинается с:
# Policy for named that uses named user and chroots to /var/named
# This policy works for the default configuration of named.
Policy: /usr/sbin/named, Emulation: native
Директива “Policy” определяет полный путь к программе, для которой создана эта политика. Вы не сможете обмануть systrace, подсунув ей другую программу с таким же именем из другого места системы. Запись “Emulation” показывает, для какого ABI эта политика. Запомните, что системы BSD могут использовать ABI множества операционных систем. Теоретически, systrace может управлять доступом к системным вызовам любых ABI, тем не менее, на данный момент поддерживаются только двоичный родной и Linux.
Оставшиеся строки определяют системные вызовы, которые программа может и не может использовать. По умолчанию, политика для named содержит 73 строки правил системных вызовов. Самая простая выглядит примерно так:
native-accept: permit
Когда /usr/sbin/named пытается использовать системный вызов accept() под родным ABI, он разрешается. Что такое accept()? Запустите man 2 accept и вы узнаете, что это вызов-разрешение подключения к сокету. Ясное дело, что сервер имен будет разрешать подключения к сетевому сокету!
Другие правила более жестки. Вот, например, правило для системного вызова bind(), который разрешает программе запросить TCP/IP-порт для подключения:
native-bind: sockaddr match "inet-*:53" then permit.
Sockaddr — это имя аргумента, получаемое системным вызовов accept(). Fnmatch говорит systrace сравнить полученную переменную со строкой inet-*:53 в соответствии со стандартными правилами. Таким образом, если переменная sockaddr соответствует строке inet-*:53, то соединение разрешается. Программа может использовать порт 53 UDP и TCP. Если у атакующего имеется эксплоит, заставляющий named подключить командную строку к порту с большим номером, эта политика systrace помешает эксплоиту сработать без изменений кода named!
native-chdir: filename eq "/" then permit
native-chdir: filename eq "/namedb" then permit
На первый взгляд, это кажется неправильным. Ключ eq сравнивает друг с другом две строки и требует точного соответствия. Если программа попытается пойти в корневую директорию или же в директорию /namedb, systrace разрешит это. Почему же мы хотим разрешить named доступ к корневой директории? Следующая часть кода объясняет это:
native-chroot: filename eq "/var/named" then permit
Мы можем использовать родной системный вызов chroot() для переименования нашей корневой директории в /var/named, но не для других директорий. В данный момент директория /namedb является на самом деле /var/named/namedb, которая является чувствительным к доступу размещением named с примененным chroot. Нам также известно, что логи named ведутся в /var/log/messages. Как же оно тогда работает, если chroot применен к программе в /var/named?
native-connect: sockaddr eq "/dev/log" then permit
Эта программа может использовать родной системный вызов connect() для общения с /dev/log и только /dev/log. Эта конструкция «отталкивает» внешние подключения. Если вы не заметили, что это и есть способ, которым ведутся логи программы, возможно, это немного смутит вас. Тем не менее, программа запускается с измененным корнем; /dev/log открывается до chroot и chroot не отменяет доступ к открытым файлам из внешней (без применения chroot) области.
Также видны несколько записей для несуществующих системных вызовов.
native-fsread: filename eq "/" then permit
native-fsread: filename eq "/dev/arandom" then permit
native-fsread: filename eq "/etc/group" then permit
Systrace объединяет схожие системные вызовы в группы. Вы можете отключить эту функцию с помощью переключателя из командной строки и использовать только те системные вызовы, которые вы сами определите, тем не менее, в большинстве случаев эти группы довольно удобны и существенно упрощают ваши политики. Давайте рассмотрим группы fsread и fswrite. Fsread — это псевдоним для stat(), lstat(), readlink () и access() под родным и linux ABI. Fswrite — псевдоним для unlink(), mkdir() и rmdir() в родном и linux ABI. Так как open() может использоваться для чтения или записи файла, он связан как с fsread, так и с fswrite, в зависимости от того, как он вызван. Итак, named может читать некоторые файлы из /etc, список корневой директории и получать доступ к файлу групп.
Systrace поддерживает два опциональных ключа в конце оператора политик: errorcode и log. Errorcode — это ошибка, возвращаемая когда программа пытается получить доступ к этому системному вызову. Программы будут вести себя по-разному, в зависимости от того, какую ошибку получат; named будет по-разному реагировать на ошибки «нет разрешения» и «не хватает памяти». Полный список ошибок вы можете получить от errno(2). Используйте не номер ошибки, а имя. Например, вот так мы возвращаем ошибку о не несуществующих файлах:
filename sub "<non-existent filename>" then deny[enoent]
Если вы добавите слово log в конец правила, то успешные системные вызовы будут заноситься в логи. Например, если мы захотим, чтобы каждое подключение named(8) к порту 53 заносилось в лог, нам понадобится отредактировать оператор политики для вызова bind():
native-bind: sockaddr match "inet-*:53" then permit log
Вы также можете осуществить фильтрацию на основе user ID или group ID, как показано в примере:
native-setgid: gid eq "70" then permit
В этом обзоре рассмотрены почти все правила, которые вам могут понадобиться. Так же, как и большинство вещей в компьютерном мире, для выполнения 90% задач systrace требуется только 10% его возможностей. Если вы хотите узнать больше о systrace, почитайте systrace(1).
Итак, теперь вы можете распознать с первого взгляда политику systrace.
Для начала разберемся, что же такое системные вызовы? Многие администраторы были замечены за употреблением этого термина, но, к сожалению, не все они понимают в полной мере, что он означает.
Системный вызов — это функция, позволяющая вам обмениваться информацией с ядром операционной системы.
Если вы хотите получить под что-либо память, открыть TCP-порт или произвести дисковую операцию чтения/записи, то это и будет системным вызовом. Некоторые системные вызовы описаны во второй части этой статьи.
Unix также поддерживает большое количество вызовов библиотек С. Часто их путают с системными вызовами, но на самом деле это всего лишь стандартизованные куски кода, выполняющие некоторые часто используемые действия. Например, вы легко можете сами написать функцию, вычисляющую квадратные корни, но для того, чтобы, например, выделить под что-либо память, вам обязательно потребуется системный вызов. Если вы сомневаетесь, является ли какая-либо функция системным вызовом или функцией библиотеки С, воспользуйтесь онлайновым руководством.
Возможно, вы столкнетесь с какой-либо редкой функцией, не документированной в онлайновом руководстве, например, break(). Чтобы идентифицировать этот класс, вам придется покопаться в других источниках (break() является очень старым системным вызовом, используемым совместно с libdc, а не программистами. Скорее всего, поэтому его пропустили при написании man-страниц для людей).
Systrace запрещает все явно не разрешенные действия, и ведет логи запрещений в syslog. Если в программе, запущенной под systrace, возникает проблема, вы можете найти, какой системный вызов необходим программе, и решить, добавлять его в политику, переконфигурировать программу, или жить дальше с этой ошибкой.
Systrace имеет несколько важных частей: политики, инструмент создания политик, инструмент настройки доступа во время рабочего цикла и интерфейс реального времени для администратора.
чтение политик systrace
Руководство sysrtace включает полное описание синтаксиса, используемого для описания политик, но я считаю, что будет гораздо проще и понятнее разобрать пример работающей политики, а уже потом подробней остановиться на синтаксисе.
Давайте посмотрим на политику, предоставляемую OpenBSD 3.2 для named.
Перед ознакомлением с политикой для named, давайте рассмотрим некоторые общеизвестные факты о требованиях доступа к DNS-серверу. Трансферы зон осуществляются через порт 53/TCP, в то время как основные запросы обслуживаются на 53-м UDP-порту. OpenBSD по умолчанию делает chroot для named в /var/named и ведет все логи в /var/log/messages. Следовательно, можно ожидать, что системным вызовам понадобится доступ туда.
Каждый файл политик systrace находится в файле, имеющем название, совпадающее с полным путем к программе, за исключением того, что все слэши в нем заменены на подчеркивания. Файл политики usr_sbin_named содержит несколько записей разрешения доступа. Файл начинается с:
# Policy for named that uses named user and chroots to /var/named
# This policy works for the default configuration of named.
Policy: /usr/sbin/named, Emulation: native
Директива “Policy” определяет полный путь к программе, для которой создана эта политика. Вы не сможете обмануть systrace, подсунув ей другую программу с таким же именем из другого места системы. Запись “Emulation” показывает, для какого ABI эта политика. Запомните, что системы BSD могут использовать ABI множества операционных систем. Теоретически, systrace может управлять доступом к системным вызовам любых ABI, тем не менее, на данный момент поддерживаются только двоичный родной и Linux.
Оставшиеся строки определяют системные вызовы, которые программа может и не может использовать. По умолчанию, политика для named содержит 73 строки правил системных вызовов. Самая простая выглядит примерно так:
native-accept: permit
Когда /usr/sbin/named пытается использовать системный вызов accept() под родным ABI, он разрешается. Что такое accept()? Запустите man 2 accept и вы узнаете, что это вызов-разрешение подключения к сокету. Ясное дело, что сервер имен будет разрешать подключения к сетевому сокету!
Другие правила более жестки. Вот, например, правило для системного вызова bind(), который разрешает программе запросить TCP/IP-порт для подключения:
native-bind: sockaddr match "inet-*:53" then permit.
Sockaddr — это имя аргумента, получаемое системным вызовов accept(). Fnmatch говорит systrace сравнить полученную переменную со строкой inet-*:53 в соответствии со стандартными правилами. Таким образом, если переменная sockaddr соответствует строке inet-*:53, то соединение разрешается. Программа может использовать порт 53 UDP и TCP. Если у атакующего имеется эксплоит, заставляющий named подключить командную строку к порту с большим номером, эта политика systrace помешает эксплоиту сработать без изменений кода named!
native-chdir: filename eq "/" then permit
native-chdir: filename eq "/namedb" then permit
На первый взгляд, это кажется неправильным. Ключ eq сравнивает друг с другом две строки и требует точного соответствия. Если программа попытается пойти в корневую директорию или же в директорию /namedb, systrace разрешит это. Почему же мы хотим разрешить named доступ к корневой директории? Следующая часть кода объясняет это:
native-chroot: filename eq "/var/named" then permit
Мы можем использовать родной системный вызов chroot() для переименования нашей корневой директории в /var/named, но не для других директорий. В данный момент директория /namedb является на самом деле /var/named/namedb, которая является чувствительным к доступу размещением named с примененным chroot. Нам также известно, что логи named ведутся в /var/log/messages. Как же оно тогда работает, если chroot применен к программе в /var/named?
native-connect: sockaddr eq "/dev/log" then permit
Эта программа может использовать родной системный вызов connect() для общения с /dev/log и только /dev/log. Эта конструкция «отталкивает» внешние подключения. Если вы не заметили, что это и есть способ, которым ведутся логи программы, возможно, это немного смутит вас. Тем не менее, программа запускается с измененным корнем; /dev/log открывается до chroot и chroot не отменяет доступ к открытым файлам из внешней (без применения chroot) области.
Также видны несколько записей для несуществующих системных вызовов.
native-fsread: filename eq "/" then permit
native-fsread: filename eq "/dev/arandom" then permit
native-fsread: filename eq "/etc/group" then permit
Systrace объединяет схожие системные вызовы в группы. Вы можете отключить эту функцию с помощью переключателя из командной строки и использовать только те системные вызовы, которые вы сами определите, тем не менее, в большинстве случаев эти группы довольно удобны и существенно упрощают ваши политики. Давайте рассмотрим группы fsread и fswrite. Fsread — это псевдоним для stat(), lstat(), readlink () и access() под родным и linux ABI. Fswrite — псевдоним для unlink(), mkdir() и rmdir() в родном и linux ABI. Так как open() может использоваться для чтения или записи файла, он связан как с fsread, так и с fswrite, в зависимости от того, как он вызван. Итак, named может читать некоторые файлы из /etc, список корневой директории и получать доступ к файлу групп.
Systrace поддерживает два опциональных ключа в конце оператора политик: errorcode и log. Errorcode — это ошибка, возвращаемая когда программа пытается получить доступ к этому системному вызову. Программы будут вести себя по-разному, в зависимости от того, какую ошибку получат; named будет по-разному реагировать на ошибки «нет разрешения» и «не хватает памяти». Полный список ошибок вы можете получить от errno(2). Используйте не номер ошибки, а имя. Например, вот так мы возвращаем ошибку о не несуществующих файлах:
filename sub "<non-existent filename>" then deny[enoent]
Если вы добавите слово log в конец правила, то успешные системные вызовы будут заноситься в логи. Например, если мы захотим, чтобы каждое подключение named(8) к порту 53 заносилось в лог, нам понадобится отредактировать оператор политики для вызова bind():
native-bind: sockaddr match "inet-*:53" then permit log
Вы также можете осуществить фильтрацию на основе user ID или group ID, как показано в примере:
native-setgid: gid eq "70" then permit
В этом обзоре рассмотрены почти все правила, которые вам могут понадобиться. Так же, как и большинство вещей в компьютерном мире, для выполнения 90% задач systrace требуется только 10% его возможностей. Если вы хотите узнать больше о systrace, почитайте systrace(1).
Итак, теперь вы можете распознать с первого взгляда политику systrace.
Майкл Лукас, перевод Дмитрия Герусса.
Сетевые решения. Статья была опубликована в номере 08 за 2003 год в рубрике sysadmin