Илья Медведовский - Атака на Internet
В-четвертых, опасным заблуждением является расчет на то, что ограничения, установленные в полях формы, а также значения спрятанных полей не будут никем модифицированы. В действительности же никто не может помешать пользователю подсмотреть значения скрытых полей в исходном коде страницы, скопировать страницу на свой диск, слегка модифицировать по своему усмотрению и проверить скрипт на прочность.
Первым барьером на этом пути обычно служит проверка переменной окружения HTTP_REFERER, хранящей URL страницы, с которой был вызван скрипт. Если HTTP_REFERER указывает на адрес, не имеющий ничего общего с адресом нашего сайта, можно смело игнорировать этот вызов, рассматривая его как атаку.
Использовать HTTP_REFERER эффективно в борьбе со спаммерами, засоряющими гостевые книги, доски объявлений и т. п. Конечно, в большинстве случаев помогает блокировка соответствующего IP-адреса, сочетающаяся с установкой cookie, однако более настойчивый пользователь может подготовить html-код с формой, вызывающей ваш скрипт гостевой книги, и пару строк на JavaScript, автоматически отправляющих эту форму, после чего разместить html-код в чужих гостевых книгах, разослать его по телеконференциям и т. п. При отсутствии проверки на HTTP_REFERER несколько веселых дней вам гарантировано.
К сожалению, не все так хорошо и с проверкой. Переменная HTTP_REFERER получает свое значение из http-заголовка Referer, передаваемого клиентом. Если в роли клиента выступает обычный браузер, этот заголовок действительно заполняется так, как мы и предполагали. Но проблема в том, что никто не помешает воспользоваться клиентом, ведущим себя менее добросовестно, к примеру, вот таким скриптом:#!/usr/bin/perl -w
use Socket;
$proto = getprotobyname(’tcp’);
socket(Socket_Handle, PF_INET, SOCK_STREAM, $proto);
$port = 80;
$host = "www.victim.com";
$sin = sockaddr_in($port,inet_aton($host));
connect(Socket_Handle,$sin);
send Socket_Handle,"GET /cgi-bin/env.cgi?param1=val1¶m2=val2 HTTP/1.0n",0;
send Socket_Handle,"Referer: any referer you wishn",0;
send Socket_Handle,"User-Agent: my agentn",0;
send Socket_Handle,"n",0;
while (<Socket_Handle>)
{
print $_;
}В нашем случае имитируется отправление данных формы методом GET, но для имитации метода POST (пример работы с POST приведен далее) тоже нет серьезных препятствий. С точки зрения безопасности эти методы примерно равнозначны. Некоторое предпочтение можно отдать POST, поскольку GET передает всю информацию непосредственно в URL, что делает ее более доступной для перехвата. Представим ситуацию, когда некоторая форма требует ввода имени и пароля и передает их методом GET. Далее динамически формируется страница, имеющая ссылку на другой сервер. Если посетитель уйдет по этой ссылке, то в качестве Referer в log-файл сервера будет записан тот самый URL, в котором открыто прописаны имя пользователя и его пароль. Опять же GET легче поддается имитации – для его подделки необязательно копировать и модифицировать код формы, достаточно набрать в адресной строке браузера подходящий URL.
В-пятых, ошибочно хранить критичную информацию в открытых для доступа файлах. Можно, конечно, утешать себя мыслью, что адреса файлов еще надо определить, но в любом случае это решение нельзя признать удачным: всегда есть шанс, что из-за плохой конфигурации сервера станет возможным просмотр списка файлов в каталоге и наша информация будет выставлена на всеобщее обозрение; нельзя исключать возможность распространения нашего скрипта – он завоюет популярность, его исходные тексты станут доступными по всей сети, и месторасположение секретных файлов опять-таки перестанет быть тайной. Поэтому файлы с критичной информацией желательно располагать в местах, по возможности вынесенных за пределы дерева каталогов Web-сервера или хотя бы защищенных от чтения (например, при использовании Apache этого можно добиться, разместив в защищаемом каталоге файл. htaccess со строкой deny all внутри).
Источник многих проблем для сайтов с установленными гостевыми книгами (или аналогичными скриптами) – html-тэги. Разрешив пользователю ввод тэгов, вы тем самым провоцируете атаку и на других пользователей, и на сервер. Последнее возможно в случае, если сервер сконфигурирован таким образом, что файлы, создаваемые скриптом, допускают использование SSI (Server Side Includes – директивы включения на стороне сервера). SSI позволяют вставить в html-документ результат выполнения некоторой программы, содержимое другого файла, значение переменной окружения и т. д. Директивы SSI имеют следующий вид:<!–# команда параметр =" аргумент "–>
Например:
<!–#include virtual="/some.html"–> <!–#exec cgi="/cgi-bin/some.cgi"–>
Чтобы не допускать к использованию SSI всех посетителей сервера, можно разрешить SSI-директивы только в файлах с определенным расширением (обычно *.shtml), тогда в файлах *.html, создаваемых скриптами, команды SSI будут восприниматься как простой комментарий. Однако подобное решение далеко не всегда устроит разработчика сайта. Следующий способ – полная фильтрация тэгов. Самое простое – заменить все символы «<» и «>» на коды «<» и «>» соответственно:
$string =~ s/</</g; $string =~ s/>/>/g;
Другой вариант – удалить все, что находится внутри угловых скобок:
$string =~ s/<([^>]|n)*>//g;
Опять же не все Web-мастера желают лишать своих посетителей возможности вставки кодов для красивого оформления текста. Тогда последнее, что остается сделать, – фильтровать часть кодов, оставляя лишь самые «безопасные». Это наиболее трудоемкий и потенциально опасный путь:
@BADTAG = (
"applet",
"script",
"iframe",
#и т. д., все "опасные" тэги
"img"
);
foreach $t(@BADTAG)
{
$string =~ s/<$t/<none/gi;
$string =~ s/</$t/</none/gi;
}Такой подход чреват некоторыми опасностями. Например, известный скрипт formmail Мэтта Райта в последнее время фильтрует SSI следующим образом:
value =~ s/<!–(.|n)*–>//g;
Это несомненный прогресс по сравнению с первыми версиями, просто пропускавшими SSI. Однако проблема в том, что Apache, по крайней мере, не требует присутствия закрывающих символов «->» для выполнения указанной директивы, поэтому злоумышленник может добиться своего, просто введя строку <!–#include virtual=”some.html”.
Казалось бы, можно справиться с проблемой, «выкусывая» <!– (value =~ s/<!–//g;), но и в этом случае остается обходной маневр: строка <<!–!–#include virtual=”some.html” в итоге преобразуется в <!–#include virtual=”some.html”.
Достаточно безопасной можно считать конструкцию while(s/<!–//g){}, хотя и у нее есть свои минусы. Похожие проблемы возникают на серверах, использующих ASP, PHP и т. д., причем большинство свободно распространяемых скриптов их просто игнорирует. Будьте крайне осторожны, адаптируя готовый скрипт к своему серверу, если он (сервер) умеет чуть больше, чем просто возвращать html-документы.
Создание безопасных CGI-приложений
Рассмотрим основные рекомендации по созданию безопасных CGI-приложений на Perl, позволяющие справиться с вышеописанными проблемами.
При использовании sendmail избавиться от ошибки очень легко – достаточно применить ключ «-t», запрещающий использовать адрес, переданный в командной строке, и передать его в заголовке письма:...$mailprog=’| /usr/sbin/sendmail -t’;
open (MAIL,"$mailprog ");
print MAIL "To: $addressnFrom: $fromnSubject: Confirmationnn";
print MAIL "Your request was successfully receivedn";
receivedn";
close MAIL;
Если же никак не удается избавиться от необходимости передачи пользовательского ввода оболочке, остается фильтровать в нем все специальные символы. Этих символов довольно много: <>|&;`’”*$?~^()[]{}nr. Самое простое, что можно сделать, – удалить все спецсимволы из введенной строки с помощью конструкции примерно такого вида:
$metasymbols = «][<>|&;`’»*$?~^(){}nr"; $string =~ s/[$metasymbols\]//g;
Помимо этого постарайтесь гарантировать соответствие ввода предусмотренному шаблону. Скажем, для того же почтового адреса этим шаблоном может быть [email protected], что чаще всего делается на Perl следующим образом:
die «Wrong address» if ($address !~ /^w[w-.]*@[w-.]+$/);
Здесь в начале и в конце строки ожидается один или несколько символов «a» – «z», «A» – «Z», «0» – «9», «-», «.» и «@» внутри, причем «-» или «.» не могут быть первыми. Правда, это не слишком помогает против атак, подобных приведенной выше, достаточно завершить наш псевдоадрес чем-нибудь типа ;@somewhere.ru. Если же у вас нет желания фильтровать спецсимволы, можно использовать другой вариант вызова функций system и exec, позволяющий передать не один аргумент, а список аргументов. В этом случае Perl не передает список аргументов в оболочку, а рассматривает первый аргумент как подлежащую выполнению команду и остальные аргументы – как параметры этой команды. Причем обычная для оболочки интерпретация спецсимволов не производится:
вместо system «grep $pattern $files»; использовать system «grep», «$pattern», «$files»;.
Этим же свойством можно воспользоваться для безопасного перенаправленного ввода/вывода. При этом нам понадобится знание того факта, что при открытии с перенаправлением вывода команды «-» мы неявно вызываем fork, создавая тем самым копию нашего процесса. В такой ситуации функция open возвращает 0 для дочернего процесса и pid дочернего процесса для родительского, что позволяет применять оператор or: