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

       

Обращаем внимание на неожиданные или несанкционированные изменения


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

файлами /bin/login, msgina.dll или Finder, то вы, заметив это, будете настолько счастливы, что простите все предыдущие ложные тревоги.

Изменения локальной файловой системы

Файловые системы - это отличное место для начала исследований программ, следящих за изменениями. Мы собираемся исследовать способы проверки неизменности важных файлов, в частности, исполняемых файлов операционной системы или файлов, связанных с безопасностью

(например /etc/passwd или msgina.dll). Изменения, внесенные в эти файлы без ведома администратора, часто являются признаками вмешательства злоумышленника. В сети существует ряд довольно сложных инструментов, которые устанавливают троянские версии важных файлов и заметают следы. Это самые злобные изменения, которые можно обнаружить. С другой стороны, иногда просто полезно знать, что важные файлы изменились (особенно если одну и ту же систему администрируют несколько человек). Технологии, которые мы рассмотрим, будут

одинаково хорошо работать в обоих случаях.

Самый простой способ выяснить, был ли файл изменен - использовать функции stat() и lstat(). Эти функции принимают имя файла или файловый дескриптор и возвращают массив с информацией об этом файле. Единственное различие между этими двумя функциями проявляется в операционных системах, подобных Unix, поддерживающих символические ссылки. В таких случаях 1 stat () применяется для получения информации о файле, на который указывает ссылка, а не о самой символической ссылке. На всех остальных операционных системах информация, возвращаемая функцией lstat(), будет совпадать с информацией, возвращаемой функцией stat().




Использовать stat() или lstat() очень просто:

@information = stat("filename");

Как сказано в главе 3 «Учетные записи пользователей», можно также применять модуль File: :Stat Тома Кристиансена, чтобы получить эту же информацию, используя объектно-ориентированный синтаксис.



Информация, возвращаемая функциями stat() и lstat(), зависит от операционной системы. stat() и lstat() происходят от системных вызовов в Unix, поэтому Perl-документация по этим функциям ссылается на значения, возвращаемые в Unix. Можно посмотреть (табл. 10.1). как эти значения соотносятся с тем, что возвращается функцией в Windows NT/2000 и MacOS. В первых двух столбцах приведены порядковый номер поля и его описание для систем Unix.

Таблица 10.1. Сравнение значений, возвращаемых функцией stat( )







Описание поля в Unix



Действительно в

NT/2000



Действительно в MacOS

0

Номер устройства файловой системы

Да (порядковый но-

мер диска)

Да (но является vRefNum)

1

Inode

Нет (всегда 0)

Да (но filelD/dirlD)

2

Режим файла (тип и права)

Да

Да (но 777 для каталогов и приложений, 666 для незаблокированных документов, 444 для заблокированных документов)

3

Количество (жестких) ссылок на файл

Да (для NTFS)

Нет (всегда 1)

4

Численный идентификатор владельца файла

Нет (всегда 0)

Нет (всегда 0)

5

Численный идентификатор группы владельца файла

Нет (всегда 0)

Нет (всегда 0)

6

Идентификатор устройства (только для специальных файлов)

Да (порядковый номер диска)

Нет (всегда null)

7

Размер файла в байтах

Да (но не включает размер каких-либо альтернативных

потоков данных)

Да (но возвращает только размер данных)

8

Время последнего доступа относительно начала эпохи

Да

Да (только эпоха начинается на 66 лет раньше, чем в Unix, то есть 1/1/1904, и значение то же, что и для поля №9) "

9

Время последней модификации относительно начала эпохи

Да

Да (только эпоха начинается 1/1/1904 и значение то же, что и для поля №8)

10

Время последнего изменения inode относительно начала эпохи

Да (но время создания файла)

Да (только эпоха начинается 1/1/1904, и это время создания файла)

11

Предпочтительный размер блока для ввода/вывода

Нет (всегда null)

Да

12

Количество занятых блоков

Нет (всегда null)

Да
<


Для возвращения атрибутов, специфичных для операционной системы, в других He-Unix-версиях Perl помимо stat() и lstat() используются специальные функции. Рассказ о таких функциях, как Perl::Getr41oInf u() и Win32: :FileSecunу : ujt.(), можно найти в главе 2 «Файловые системы».

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

Программа принимает либо аргумент -р filename, чтобы вывести значения lstat() для заданного файла, либо аргумент -с filename, чтобы проверить значения lstat() для всех файлов, перечисленных в filename.

use Getopt::Std;

используем это для создания более симпатичного вывода позже в &pnntchanged()

@statnames = qw(dev ino mode nlink uid gid rdev size mtime ctime blksize blocks);

getopt('p:c:');

die "Использование: $0 [-p <filename>|-c <filena:iie>]\n" unless ($opt_p or $opt_c);

if ($opt_p)(

die "Невозможно получить информацию о файле $opt._p:

unless (-с $opt_p): print $opt_p."|",]OinC |',(lstat($opt_p))[0..7,9..12]),"\n":

exit:

if ($opt_c){

oper.(CFILE,$opt_c) or

die "Невозможно открыть файл $opt_c:$!\": while(<CFILE>){ cho;:!p:

ssavecstats = spl-'('\:' ); die Неверное количество полей в строке, начинающейся с

$savedstats[C']\'T jniess (Sttsaveustits == 12): s<,i.r rentstats = (Isratv $savedstat3[0]) )[0. 7,9. 12]

}

close(CFILE);

}

sub printchanged{

my($saved. $curre!it)= ®_:

выводим имя файла после того. выбрасываем его из массива, прочитанного из файла

prin: shift §{$saved}.":\n":

for (my $i=0; $1 < $#{$saved};$!++){

if ($saved->[$i] ne $current->[$i]){



print "\t".$statnames[$i]." is now ".$current->[$i];

print " (should be ".$saved->[$i].")\n":

} }

Для использования этой программы можно набрать checkfile -p /etc/passwd » checksumfile. В файле checksumfile теперь будет храниться строка, которая выглядит так:

/etc/passwd|1792|11427]33060)1|0|0|24959|607|921016509|921016509|8192|2

Этот шаг нужно повторить для каждого файла, за которым мы наблюдаем. Затем вызов сценария с аргументом checkfile -с checksumfile будет сообщать обо всех изменениях. Например, если я удалю один символ из /etc/passwd, сценарию это не понравится, и он выведет такое сообщение:

/etc/passwd:

size is now 606 (should be 607)

mtime is now 921020731 (should be 921016509)

сtime is now 921020731 (should be 921016509)

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

if ("®savedstats[1..12]' ne "@currentstats"):

Perl автоматически преобразовывает список в строку, склеивая элементы списка через пробел:

joint" ",ssavedstats[1. . 12]))

и затем уже сравнивает получившиеся строки. Этот прием хорошо работает для коротких списков, в которых имеет значение порядок и количество элементов. В большинстве других случаев необходимо использовать итеративный подход или хэши, как описано в списках часто задаваемых вопросов perlfaq, входящих в состав Perl.

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

Обнаружение изменений в данных - это одна из сильных сторон алгоритмов, известных как криптографические хэш-функции («message-di-gest algorithms»). Вот как Рон Райвест (Ron Rivest) описывает алгоритм «RSA Data Security, Inc. MD5 Message-Digest Algorithm» в RFC1321:



Алгоритм на вводе принимает сообщение произвольной длины и создает подпись (message digest или fingerprint) длиной 128 бит. Считается, что просто невозможно создать два сообщения, у которых совпадали бы подписи; также невозможно создать сообщение, подпись которого совпадала бы с заранее заданной.

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

этой чудесной возможностью из Perl - применить модуль LUgos, : : МУ:> из семейства модулей Digest.

Использовать модуль Digest:: MD5 просто. Нужно создать объект, добавить в него данные при помощи методов add () miHaridfile(), а затем попросить модуль создать подпись.

Можно сделать нечто подобное для подсчета подписи MD5 для файла паролей в Unix:

use Digest: :MD5 qw(rnd5); $md5 = new Digest::MD5:

open(PASSWD. "/Gtc/f.'asswd") or die "Невозмонсть открыть pass,vd$' ":

acdf :Ie(PASSWD):

ciose(PASSlvD)

prit 3r!d5->hexd:g-2s: . "v "",

В документации по Digest: :MD5 сказано, что для создания более компактных программ можно связывать несколько методов вместе. Так, предыдущую программу можно переписать:

Файлы perlfaql. pod, perlfaq2.pod ... perlfaq[N].pod.

use Digest::MD5 qw(md5);

open(PASSWD. Vei-c/pabswri") or 'I:e "Ненот.' print Digest: :MD5--'neiA->addfi](](PASSWD) close(PASSWD):

Обе программы выводят следующее:

a6f905e6h45a65a7e03dOS09448b501c

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

335679с4с97а381523034331a06df3e7

Теперь любые изменения становятся очевидными. Давайте расширим предыдущую программу проверки атрибутов и добавим к ней MD5:

use Getopt::Std;

use Digest::MD5 qw(md5);

@statnames =

qw(dev ino mode nlink uid gid rdev size mtime ctinie blksize blocks md5):



getopt('p:c:');

die "Использование: 0 [-p <filename>|-c <fileriame>]\n"

unless ($opt_p or $opt_c):

if ($opt^.p){

die "Невозможно получить информацию о файле $opt_p:$'\n"

unless (e $opt_p);

open(F.$opt_p) or die "Невозможно открыть $opt_p:$'\r";

$d.igest = Digest: ;MD5->new-^addfile(F)->hexaigest:

ciose(F):

print $opt__p, "|", ]oin

"|$digest", "\n":

exit:

}

if ($ppt_c){

open(CFILE.$opt_c) or

die "Невозможно открыть Файл;.

wnilc (<CFILE>){

c'-.o^p:

ssavedstats = spli r(

far,, rp'.tstats = (lstat($s;ivenstars[OJ))[0 . Л9..121:

doSi:( h )

&pr unchanged (\3savudstats. Vicur: и its: a; .1)

if ("«?savedstars[1 13]"

close(CFILE):

}

sub printcharigcd {

my($saved,$cnrrent)= ®_:

print shift @{$saved).":\n";

for (my $i=0; $1 <= $(({$saved}; $!++){

if ($saved->[$i] ne $current->[$i]){

print " P\$statnames[$i]." is now ", $current->[$i ]:

print " (".$saved->[$i].")\n";

}



Изменения сетевых служб



Мы узнали, как обнаружить изменения в локальных файловых системах. Как насчет того, чтобы заметить изменения на других машинах или в службах, ими поддерживаемых? Мы уже видели способы запроса NIS и DNS в главе 5 «Службы имен TCP/IP». Не должна вызвать затруднений проверка изменений в повторяющихся запросах к этим службам. Например, можно притвориться вторичным сервером и запросить копию данных (т. е. выполнить зонную пересылку) с сервера для определенного домена, если, конечно, DNS-сервер настроен так, что позволит сделать это:

use Net::DNS;

принимает два аргумента в командной строке: первый - сервер

 имен, к которому посылается запрос, а второй - интересующий

и нас доме"

$server = new Net::DNS::Resolver.

$server-:^.ar;eservers($ARGV[C]);

ur;n+ 3 ' OERR 'Выполняется ''еррдоча.

i:c $sorver-.'Or rorst гц-.д jr,:ess (de'ired ьу(У-(-}\

for Srecord (@zone){

$record-'>pr int;



}

Объединим эту идею с MD5. Вместо того чтобы получать информацию о зоне, давайте просто сгенерируем для нее подпись:

use Net::DNS:

use FreezeTnaw qiv{f reeze!;

use Digest::MD5 qw(md5):

Sserver = new Net: :DNS::Resolver:

$server->naineservers($ARGV[0]):

print STDERR "Выполняется передача,..";

@zone = $server->axfr($ARGV[1]);

die $server->errorstring unless (defined @zone);

print STDERR "готово.\n";

$zone = join('',sort map(freeze($_),@zone));

print "MD5 fingerprint for this zone transfer is: ";

print Digest::MD5->new->add($zone)->hexdigest,"\n";

MD5 работает со скалярными данными (сообщение), но не со структурами типа списка кэшей, как @zone. Вот почему нужна такая строчка:

Szone = join(",sort map(freeze($_),@zone));

Для преобразования каждой записи из структуры данных @zone в обычные строки воспользуемся модулем FreezeThaw, который мы уже видели в главе 9 «Журналы». Перед тем как записи будут склеены в одно большое скалярное значение, они будут отсортированы. Сортировка позволяет проигнорировать порядок, в котором возвращались записи при пересылке зоны.

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

Все, что мы видели до сих пор, не очень помогло нам преодолеть затруднения. Возможно, следует прояснить несколько вопросов:

  • Что если кто-то подделает базу данных MD5 подписей и подставит действительные подписи под поддельные троянские файлы или изменения служб?


  • Что если кто-то подделает ваш сценарий таким образом, что он будет только создавать видимость проверки подписей со значениями из базы данных?


  • Что если кто-то сделает что-нибудь с модулем MD5 на вашей системе?


  • Это уже, конечно, предел паранойи, но что если кто-то сделает что-то с самим исполняемым файлом Perl, одной из его разделяемых библиотек или самим ядром операционной системы?


  • Обычные ответы на эти вопросы (какими бы они ни были неудовлетворительными): храните хорошие копии всего, что имеет отношение к этому процессу (базы данных подписей, модули, Perl и т. д.) на устройствах, к которым разрешен доступ только для чтения.

    Эта головоломка - еще одна иллюстрация бесконечности безопасности.

    Всегда можно найти что-то, чего можно опасаться.






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