Илья Медведовский - Атака на Internet
open (MAIL, «|-») or exec $mailprog, $address;
#open в родительском процессе возвращает ненулевое значение, и нет
#необходимости выполнять правую сторону or. Дочерний же процесс
#выполняет exec, после чего завершается.
print MAIL "From: $fromnSubject: Confirmationnn";
print MAIL "Your request was successfully receivedn";
close MAIL;В перечисленных методах есть один недостаток – они требуют явного применения и определенной культуры программирования. Программист должен заставлять себя писать безопасный код, никогда не будучи до конца уверенным в отсутствии ошибок.
Perl, запущенный в так называемом зараженном режиме (tainted mode), позволяет снять часть этого гнета. Чтобы попасть в такой режим, достаточно указать параметр «-T».
После этого работа Perl приобретает несколько параноидальный характер. Все переменные, проинициализированные за пределами программы, считаются зараженными и не могут быть переданы в качестве параметров потенциально опасным функциям, таким как system, exec, eval, unlink, rename и т. д. Попытка использовать их таким образом прервет выполнение скрипта с выдачей соответствующего предупреждения.
Переменные, инициализированные вне программы, – это переменные, значения которых получены из параметров программы, со стандартного входа, из переменных среды. Причем эта «зараза» распространяется, если использовать зараженную переменную для инициализации другой переменной – та тоже станет зараженной. «Зараза» остается, даже если мы проверили переменную на отсутствие всех спецсимволов либо очистили ее от них.
Таким образом, мы устраняем возможность случайного пропуска пользовательского ввода в опасную функцию. Но как быть, если именно это нам и нужно?
Единственный способ «обеззаразить» переменную – воспользоваться регулярными выражениями и применить извлечение совпадающей подстроки при поиске по маске. Это не слишком удобно, зато торжествует принцип «все, что не разрешено, – запрещено». Заодно приобретете опыт использования регулярных выражений, что наверняка пригодится в будущем....Регулярные выражения хорошо знакомы опытным пользователям UNIX, они применяются во многих UNIX-утилитах, таких как grep, awk, sed, в редакторах (vi, emacs), в некоторых командных оболочках и т. д. Дополнительную информацию об использовании регулярных выражений можно найти практически в любой книге по Perl (например: Рэндал Шварц и Том Кристиансен «Изучаем Perl» (Randal L. Schwartzh and Tom Christiansen. Learning Perl); «Programming Perl» by Larry Wall, Tom Christiansen & Randal Schwartz и «Mastering Regular Expressions» by Jeffrey Friedl).
$address =~ /(w[w-.]*)@([w-.]+)/; $cleanaddress = $1.’@’.$2;
Все, что сопоставится выражению в первых круглых скобках, будет занесено в переменную $1, во вторых – в $2, и т. д. Переменные $1 и $2 будут уже считаться обеззараженными, и мы можем смело конструировать из них наш искомый адрес. Да, эти переменные тоже были получены из пользовательских данных, но Perl считает, что раз их значение получено из регулярного выражения, значит, они прошли нашу проверку и можно о них не беспокоиться. Чтобы быть уверенными до конца, вставим в наш код проверку:
if($address =~ /(w[w-.]*)@([w-.]+)/)
{
$cleanaddress = $1.’@’.$2;
}
else
{
warn "Wrong address: $address"; #выдавая сообщение об ошибке на stderr
$cleanaddress = "";
}Тем самым, правда, отсекаются вполне законные имена типа mama& [email protected] Менее строгая проверка вида address=~/(S+)@([w.-]+)/ пропустит и метасимволы, сведя на нет все наши усилия по обеззараживанию. У вас может возникнуть желание обеззаразить переменную следующим образом:
$address =~ (.*); $cleanaddress = $1;
Что ж, вольному – воля. Вы только что отключили все проверки и взяли всю ответственность на себя.
При использовании зараженного режима неожиданно может возникнуть ситуация, когда Perl откажется запускать внешнюю программу, поскольку переменная окружения PATH, с помощью которой определяется местоположение исполняемого модуля, тоже считается зараженной. Чтобы справиться с этим, достаточно проинициализировать ее вручную одним из следующих способов:
1. $ENV{"PATH"} = ’/bin:/usr/bin:/usr/local/bin’;
2. $ENV{"PATH"} = ’’;Ошибки в известных CGI-скриптах
Примеры некоторых таких скриптов уже приводились выше – это и печально известный phf, и formmail (кстати, все скрипты Мэтта Райта, которые можно найти на http://www.worldwidemart.com/scripts/, фильтруют SSI именно описанным выше способом). Перечислим еще несколько.
Старые версии (1.0–1.2) счетчика TextCounter, вставляющегося в страницу через CGI, получали адрес обсчитываемой страницы из переменной окружения DOCUMENT_URI, при этом не производилась проверка на метасимволы со всеми вытекающими последствиями.
Популярный счетчик wwwcount (http://www.fccc.edu/users/muquit/Count.html), написанный на С, содержал традиционную ошибку, связанную с копированием содержимого переменной окружения 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 хранятся номера сообщений, предшествовавших текущему в «потоке», а также номер текущего сообщения – чтобы можно было их скорректировать после добавления очередного сообщения: