Perl для системного администрирования

Нелегко быть всемогущим


Перед тем как продолжить, давайте отвлечемся на несколько минут и скажем пару предостерегающих слов. У программ, написанных для системного администрирования, есть одна характерная черта, которая отличает их от всех остальных программ. В Unix и Windows NT/2000 они зачастую выполняются с повышенными привилегиями, т. е. с правами пользователей root или Administrator. А такая сила подразумевает ответственность. На нас, как на программистов, ложится дополнительная ответственность писать безопасные программы. Мы пишем программы, которые могут обходить (и обходят) ограничения безопасности, накладываемые на «простых смертных». Если мы не будем осторожными, менее «нравственные» личности смогут использовать «дыры» нашего кода в низких целях. Вот несколько моментов, о которых вы всегда должны помнить, применяя Perl в этих обстоятельствах.

Не делайте этого

Конечно же, используйте Perl. Но, по возможности, старайтесь избегать выполнения программ в привилегированном окружении. Большинство задач не требует привилегий пользователя root или Admi nistrator. Например, программа анализа журналов, вероятно, не должна выполняться с правами суперпользователя. Для выполнения этих автоматизированных действий создайте другого пользователя, наделенного меньшими привилегиями. Пусть у вас будет маленькая программа, наделенная привилегиями, которая будет передавать при необходимости данные этому пользователю, и затем этот пользователь будет выполнять анализ.

Избавьтесь от своих привилегий как можно быстрее

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

В программах на Perl, выполняющихся в Unix или Linux, можно установить переменные $< и $>:

# Навсегда избавиться от привилегий ($<,$>} = (getpwnam('nobody'),getpwnam('nobody'));






В результате, реальный и эффективный идентификаторы пользователя будут установлены равными идентификатору пользователя nobody, не являющемуся привилегированным пользователем. Если вы хотите подойти к проблеме более основательно, то можете также использовать переменные $( и $) для смены реального и эффективного идентификатора группы.

В Windows NT и Windows 2000 вообще нет идентификаторов пользователей, но для избавления от привилегий существует схожий процесс. В Windows 2000 есть возможность, называемая «RunAs», которую можно использовать для запуска процесса от имени другого пользователя. В Windows NT и Windows 2000 пользователи с правами Act as part of the operating system могут выдавать себя за других пользователей. Эти права можно установить с помощью программы User Manager или User Manager for Domains:

  1. В меню Policies выберите пункт User Rights.
  2. Отметьте пункт Show Advanced User Rights.
  3. Выберите Act as part of the operating system из выпадающего списка.
  4. Выберите пункт Add... и определите пользователей или группы, которых вы хотите наделить этими правами. Если вы хотите предоставить такое право определенному пользователю, выберите пункт Show Users.
  5. Вероятно, данному пользователю придется повторно зарегистрироваться в системе, чтобы эти изменения вступили в силу.


Вам также понадобится добавить права Replace a process level token и, в некоторых случаях, Bypass traverse checking (см. документацию по Win32: : AdminMisc). Как только вы присвоили эти права пользователю, он сможет запускать сценарии на Perl с функцией LogonAs(Jser() из модуля Дэвида Рота (David Roth) Win32: :AdminMisc, который можно найти на http://www.roth.net:

use Win32::AdminMisc;

die "Невозможно персонализировать $user\n"if
(!Win32::AdminMisc::LogonAsUser('',$user,Suserpw);

Замечание: здесь существует некоторая опасность, поскольку в отличие от предыдущего примера, вы должны передать пароль пользователя в функцию LogonAsUser().



Будьте осторожны при чтении данных



При чтении важных данных, скажем, конфигурационных файлов, сначала протестируйте возможность небезопасных состояний. Например, стоит проверить, запрещена ли запись в файл и каталоги, в которых он находится (иначе, кто угодно может их испортить). Хороший способ подобной проверки можно найти в главе 8 книги «Perl Cookbook» («Perl: библиотека программиста») Тома Кристиансена (Тот Christiansen) и Натана Торкингтона (Nathan Torkington) (O'Reilly).



Другая забота - ввод пользователей. Никогда не считайте, что данным, поступающим от пользователей, можно доверять. Даже если вы явно просите пользователя: Пожалуйста, ответьте Да(У) или Нет(М):, ничто не помешает ему набрать 2049 случайных символов (либо от вредности со злым умыслом, либо потому, что его двухлетний ребенок занял освободившееся на минуту место за клавиатурой).

Ввод пользователей может быть причиной еще более серьезных проблем. Мой любимый пример- это использование нулевого байта «Poison NULL Byte», о котором сообщалось в статье о проблемах Perl в CGI. Обязательно прочитайте всю статью (ссылка на нее есть в конце этой главы). Неприятности возникают из-за отличий в обработке нулевого байта (\000) в Perl и в системных библиотеках С. Для Perl этот символ ничем не примечателен. Однако в библиотеках этот символ используется для обозначения конца строки.

На практике это означает, что у пользователя существует возможность обойти различные проверки. Один пример, приведенный в этой статье, - это программа, меняющая пароль пользователя:

if (Suser ne "root"){ <вызов соответствующей функции С>}

Если переменная $user установлена в значение root\000 (т. е. если за словом root следует нулевой байт), то приведенная выше проверка окажется удачной. Когда эта строка будет передана библиотеке, она будет воспринята просто как root, и пользователю удастся обойти проверку. Если эту ситуацию не отследить, то подобная «дыра» позволит получить доступ к произвольным файлам и другим ресурсам. Самый простой способ не пострадать от этой проблемы - подправить код, добавив что-то похожее на следующую строку:

Sinput =" tr/\000//d;

Это всего лишь один пример того, как ввод пользователя может вызвать проблемы. Именно поэтому в Perl существует специальное средство предосторожности - режим пометки (taint mode). Изучите страницу руководства perlsec, входящую в состав дистрибутива Perl, чтобы ознакомиться с отличным объяснением того, что такое отмеченные данные, а также с другими мерами предосторожности.



Будьте осторожны при записи данных

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

В первый класс попадают программы, дописывающие данные в существующие файлы. Перед дописыванием в файл в вашей программе должна быть выполнена следующая последовательность шагов:

  1. Используйте функцию stat() и обычные операторы проверки файлов для проверки атрибутов файлов. Убедитесь, что файл не является ни жесткой, ни символической ссылкой, что у него установлены нужные права и владельцы и т. д.
  2. Откройте файл для дописывания.
  3. Передайте файловый дескриптор функции stat().
  4. Сравните значения, полученные на шагах 1 и 3, чтобы убедиться, что открытый файловый дескриптор соответствует нужному вам файлу.


Смотрите программу bigbuffy из главы 9 «Журналы», которая соблюдает эту последовательность шагов.

Во второй класс попадают программы, использующие временные файлы или каталоги. Вы часто видели подобный код:

open(TEMPFILE,">/tmp/temp.$$") or die "невозможно записать в /tmp/ temp.$$:$!\n";

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

В некоторых операционных системах есть библиотечные вызовы, которые генерируют имена временных файлов, используя современный алгоритм случайных значений. Чтобы проверить вашу операционную систему, вы можете запустить следующий код. Если получаемые имена кажутся вам достаточно случайными, вы можете полагаться на POSIX: :tmpnam(). Если нет, вы можете написать собственную функцию генерации случайных имен файлов:



use POSIX qw(Tr4pnar-'): for (1..20){ print POSIX::tmpnam(),"\n"; }

Как только у вас будет имя файла, которое нельзя отгадать, вам нужно будет открыть его безопасным образом:

sysopen(TEMPFILE,$tmpname,0_RDWR|0_CREATjO_EXCL0666);

Существует другой, более простой способ выполнить эти же два шага (получить имя и открыть временный файл). Метод 10: : File->new_trnpfi-1е() из модуля 10: : File не только подберет хорошее имя (если системные библиотеки это поддерживают), но и откроет файл для чтения и записи.

Примеры использования POSIX: :tmpnam() и 10: : File->new_tmpfile(), a также другую информацию по этой теме вы можете найти в главе 7 книги рецептов «Perl Cookbook» («Perl: библиотека программиста»). В модуле File: :Temp Тима Дженнеса (Tim Jenness) также предпринимаются попытки обеспечить безопасные операции работы с временными файлами.



Избегайте состояний перехвата



По мере возможности, старайтесь не писать программ, допускающих состояния перехвата. Обычное состояние перехвата начинается с предположения, что следующая последовательность допустима:

  1. Ваша программа будет накапливать некоторые данные.
  2. Ваша программа затем будет работать с этими данными.


Если пользователи могут проникнуть в эту последовательность на шаге, скажем 1,5, и выполнить некоторую замену данных, это может привести к неприятностям. Если им удается контролировать вашу программу на шаге 2, чтобы обработать данные, отличающиеся от тех, которые были на шаге 1, значит, им удалось использовать состояние перехвата (т. е., их программа выиграла состязание, чтобы получить данные). В другом случае состояние перехвата может произойти, если вы неверно работаете с блокировкой файлов.

Состояния перехвата часто возникают в программах системного администрирования, которые на первом шаге сканируют файловую .систему, а на втором шаге изменяют данные. Бесчестные пользователи могут внести изменения в файловую систему сразу после сканирования, чтобы изменения были внесены в неверный файл. Убедитесь, что в вашем коде нет подобных «дыр».



Наслаждайтесь



Очень важно помнить, что системное администрирование интересно. Не всегда и не тогда, когда вам надо решать самые досаждающие проблемы, но определенное наслаждение в этом можно найти. Есть настоящее удовольствие в том, чтобы поддерживать других людей и создавать инфраструктуру, которая улучшает жизнь всем. Когда созданные вами программы объединяют людей - это прекрасно.

Теперь, когда вы готовы, давайте поработаем над «теми самыми проводами».






Содержание раздела