Шрифт:
if($address =~ /(\w[\w\-.]*)\@([\w\-.]+)/)
{
$cleanaddress = $1.’@’.$2;
}
else
{
warn "Wrong address: $address"; #выдавая сообщение об ошибке на stderr
$cleanaddress = "";
}Тем самым, правда, отсекаются вполне законные имена типа mama&papa@home.org. Менее строгая проверка вида address=~/(\S+)\@([\w.-]+)/ пропустит и метасимволы, сведя на нет все наши усилия по обеззараживанию. У вас может возникнуть желание обеззаразить переменную следующим образом:
$address =~ (.*); $cleanaddress = $1;
Что ж, вольному – воля. Вы только что отключили все проверки и взяли всю ответственность на себя.
При использовании зараженного режима неожиданно может возникнуть ситуация, когда Perl откажется запускать внешнюю программу, поскольку переменная окружения PATH, с помощью которой определяется местоположение исполняемого модуля, тоже считается зараженной. Чтобы справиться с этим, достаточно проинициализировать ее вручную одним из следующих способов:
1. $ENV{"PATH"} = ’/bin:/usr/bin:/usr/local/bin’;
2. $ENV{"PATH"} = ’’;Ошибки в известных CGI-скриптах
Примеры некоторых таких скриптов уже приводились выше – это и печально известный phf, и formmail (кстати, все скрипты Мэтта Райта, которые можно найти нафильтруют SSI именно описанным выше способом). Перечислим еще несколько.
Старые версии (1.0–1.2) счетчика TextCounter, вставляющегося в страницу через CGI, получали адрес обсчитываемой страницы из переменной окружения DOCUMENT_URI, при этом не производилась проверка на метасимволы со всеми вытекающими последствиями.
Популярный счетчик wwwcount , написанный на С, содержал традиционную ошибку, связанную с копированием содержимого переменной окружения QUERY_STRING в буфер фиксированной длины. Передав ему специально сформированную строку, можно было выполнить на сервере любой код. Затронуты версии 1.0–2.3. Версия 2.3 также содержала ошибку, позволяющую просмотреть на сервере любой GIF-файл с помощью следующего запроса:
http://www.victim.com/cgi-bin/Count.cgi?display=image&image=../../../../../../path_to_gif/file.gif.
Впрочем, возможность применения последней ошибки не совсем ясна… В конце 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 хранятся номера сообщений, предшествовавших текущему в «потоке», а также номер текущего сообщения – чтобы можно было их скорректировать после добавления очередного сообщения: