[LinuxFocus-icon]
Домой  |  Карта  |  Индекс  |  Поиск

Новости | Архивы | Ссылки | Про LF
Эта заметка доступна на: English  Castellano  Deutsch  Francais  Nederlands  Portugues  Russian  Turkce  

[image of the authors]
автор Frйdйric Raynal, Christophe Blaess, Christophe Grenier
<pappy(_at_)users.sourceforge.net, ccb(_at_)club-internet.fr, grenier(_at_)nef.esiea.fr>

Об авторе:

Christophe Blaess - независимый инженер по аэронавтике. Он почитатель Linux и делает большую часть своей работы на этой системе. Заведует координацией переводов man страниц, публикуемых Linux Documentation Project.

Christophe Grenier - студент 5 курса в ESIEA, где он также работает сисадмином. Страстно увлекается компьютерной безопасностью.

Frйdйric Raynal много лет использует Linux, потому что он не загрязняет окружающую среду, не использует ни гормоны, ни MSG, ни костяную муку из животных ... только тяжелый труд и хитрости.



Перевод на Русский:
Kolobynin Alexey <alexey_ak0(_at_)mail.ru>

Содержание:

 

Как избежать дыр в безопасности при разработке приложения - Часть 6: CGI скрипты

[article illustration]

Резюме:

Получить исполняемый файл, который представляет собой плохо написанный Perl скрипт ... "Существует Более Одного Способа Это Сделать!"

Предыдущие статьи из серии:


_________________ _________________ _________________

 

Веб-сервер, URI и проблемы конфигурации

 

(Весьма короткое) Введение в то, как работает веб-сервер и как построить URI

Когда клиент запрашивает HTML файл, сервер посылает ему запрошенную страницу (или сообщение об ошибке). Браузер обрабатывает HTML код, чтобы отформатировать и отобразить файл. Например, введя URL (Uniform Request Locator - унифицированный указатель информационного ресурса) http://www.linuxdoc.org/HOWTO/HOWTO-INDEX/howtos.html,
клиент подключается к серверу www.linuxdoc.org и запрашивает страницу /HOWTO/HOWTO-INDEX/howtos.html (такая ссылка называется URI - Uniform Resource Identifiers (универсальный идентификатор ресурса)), используя протокол HTTP. Если страница существует, сервер отсылает запрошенный файл. В такой статической модели, если файл присутствует на сервере, он посылается клиенту как есть, иначе посылается сообщение об ошибке (хорошо известное 404 - Not Found).

К сожалениию, такая модель не позволяет использовать интерактивность при работе с пользователем, что делает невозможным такие вещи, как e-бизнес, е-бронирование (билетов) на праздники и e-что_либо_еще.

К счастью есть решения как динамически генерировать HTML страницы. CGI (Common Gateway Interface - общий шлюзовой интерфейс) скрипты - одно из них. В этом случае URI для получения веб страниц строится немного по-другому :

http://<сервер><путьКскрипту>[?[парам_1=знач_1][...] [&парам_n=знач_n]]

Список аргументов сохраняется в переменной окружения QUERY_STRING. В данных обсотятельствах, CGI скрипт не более чем исполняемый файл. Он использует stdin (стандартный ввод) или переменную окружения QUERY_STRING, чтобы получить аргументы, переданные ему. После исполнения кода, результат выводится на stdout (стандартный вывод) и затем, передается веб-клиенту. Почти каждый язык программирования может быть использован для написания CGI скриптов (откомпилированная программа на C, Perl, скрипты shell и т.д.).

Для примера, давайте поищем в каких HOWTO на www.linuxdoc.org упоминается о ssh :

http://www.linuxdoc.org/cgi-bin/ldpsrch.cgi?svr=http%3A%2F%2Fwww.linuxdoc.org&srch=ssh&db=1&scope=0&rpt=20
Фактически, то что здесь написано гораздо проще, чем кажется. Проанализируем этот URL:

Часто имена аргументов и их значения, дают представление для чего они предназначены. Более того, содержимое страницы с ответом еще лучше поясняет их назначение.

Теперь вы знаете, что лучшая сторона CGI скриптов - возможность пользователя передавать данные через аргументы... но худшая сторона - то что плохо написанный скрипт открывает дыру в безопасности.

Вы наверное заметили странные символы, которые использует ваш любимый браузер в предыдущем запросе. Эти символы закодированы в символьном наборе ISO 8859-1 (взгляните на >man iso_8859_1). В таблице 1 представлены значения некоторых из этих кодов. Вспомним, что в серверах IIS4.0 и IIS5.0 существует весьма опасная уязвимость, называемая unicode bug, основанная на представлении символов "/" и "\" в unicode.

 

Конфигурация Apache с "SSI Server Side Include"

Server Side Include - вставки на стороне сервера - одна из возможностей веб-сервера. Она позволяет вставлять инструкции в веб-страницы, такие как включение в страницу необработанного файла или выполнение команды (оболочки или CGI скрипта).

В файле конфигурации Apache httpd.conf инструкция "AddHandler server-parsed .shtml" включает этот механизм. Часто, чтобы убрать различие между .html и .shtml, в этой инструкции используют расширение .html. Естественно, это замедляет работу сервера... Работа механизма контролируется на уровне директорий при помощи инструкций:


В присоединенном к этому документу скрипте guestbook.cgi, текст, предоставленный пользователем, включается в HTML файл без замены символов '<' и ' >' на HTML код &lt; и &gt;. Любознательный человек может передать одну из следующих инструкций:

При помощи первой,
guestbook.cgi?email=pappy&texte=%3c%21--%23printenv%20--%3e
вы получите несколько строк с информацией о системе:
DOCUMENT_ROOT=/home/web/sites/www8080
HTTP_ACCEPT=image/gif, image/jpeg, image/pjpeg, image/png, */*
HTTP_ACCEPT_CHARSET=iso-8859-1,*,utf-8
HTTP_ACCEPT_ENCODING=gzip
HTTP_ACCEPT_LANGUAGE=en, fr
HTTP_CONNECTION=Keep-Alive
HTTP_HOST=www.esiea.fr:8080
HTTP_PRAGMA=no-cache
HTTP_REFERER=http://www.esiea.fr:8080/~grenier/cgi/guestbook.cgi?
 email=&texte=%3C%21--%23include+file%3D%22guestbook.cgi%22--%3E
HTTP_USER_AGENT=Mozilla/4.76 [fr] (X11; U; Linux 2.2.16 i686)
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin
REMOTE_ADDR=194.57.201.103
REMOTE_HOST=nef.esiea.fr
REMOTE_PORT=3672
SCRIPT_FILENAME=/mnt/c/nef/grenier/public_html/cgi/guestbook.html
SERVER_ADDR=194.57.201.103
SERVER_ADMIN=master8080@nef.esiea.fr
SERVER_NAME=www.esiea.fr
SERVER_PORT=8080
SERVER_SIGNATURE=<ADDRESS>Apache/1.3.14 Server www.esiea.fr Port 8080</ADDRESS>

SERVER_SOFTWARE=Apache/1.3.14 (Unix)  (Red-Hat/Linux) PHP/3.0.18
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.0
REQUEST_METHOD=GET
QUERY_STRING=
REQUEST_URI=/~grenier/cgi/guestbook.html
SCRIPT_NAME=/~grenier/cgi/guestbook.html
DATE_LOCAL=Tuesday, 27-Feb-2001 15:33:56 CET
DATE_GMT=Tuesday, 27-Feb-2001 14:33:56 GMT
LAST_MODIFIED=Tuesday, 27-Feb-2001 15:28:05 CET
DOCUMENT_URI=/~grenier/cgi/guestbook.shtml
DOCUMENT_PATH_INFO=
USER_NAME=grenier
DOCUMENT_NAME=guestbook.shtml

Инструкция exec предоставляет вам почти что эквивалент shell:


guestbook.cgi?email=ppy&texte=%3c%21--%23exec%20cmd="cat%20/etc/passwd"%20--%3e

Не используйте "<!--#include file="/etc/passwd"-->", путь должен задаваться относительно директории, где расположен HTML файл и не должен содержать "..". Файл error_log Apache будет содержать сообщение о попытке доступа к запрещенному файлу. А пользователь сможет увидеть сообщение [an error occurred while processing this directive(при обработке инструкции возникла ошибка)] на HTML странице.

SSI редко бывают нужны, поэтому их лучше отключить на сервере. Причина рассмотренной проблемы - комбинация неправильного скрипта guestbook и SSI.

 

Perl скрипты

В этом параграфе мы рассмотрим дыры в безопасности, относящиеся к CGI скриптам, написанным на Perl. Для простоты мы не будем давать полный код примеров, а только части, которые необходимы для понимания, где находится проблема.

Каждый из наших скриптов построен по следующему шаблону:

#!/usr/bin/perl -wT
BEGIN { $ENV{PATH} = '/usr/bin:/bin' }
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # Сделаем %ENV безопаснее =:-)
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD>";
print "<TITLE>Удаленная команда</TITLE></HEAD>\n";
&ReadParse(\%input);
# теперь можно использовать $input, например, так:
# print "<p>$input{filename}</p>\n";
# #################################### #
# Начало описания проблемы             #
# #################################### #



# ################################## #
# Конец описания проблемы            #
# ################################## #

form:
print "<form action=\"$ENV{'SCRIPT_NAME'}\">\n";
print "<input type=text name=filename>\n </form>\n";
print "</BODY>\n";
print "</HTML>\n";
exit(0);

# Первый аргумент должен быть ссылкой на хэш.
# Хэш будет заполнен данными.
sub ReadParse($) {
  my $in=shift;
  my ($i, $key, $val);
  my $in_first;
  my @in_second;

  # Считывание данных
  if ($ENV{'REQUEST_METHOD'} eq "GET") {
    $in_first = $ENV{'QUERY_STRING'};
  } elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
    read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'});
  }else{
    die "ОШИБКА: неизвестный метод запроса\n";
  }

  @in_second = split(/&/,$in_first);

  foreach $i (0 .. $#in_second) {
    # Замена плюсов пробелами
    $in_second[$i] =~ s/\+/ /g;

    # Разбиение на ключ и значение
    ($key, $val) = split(/=/,$in_second[$i],2);

    # Перевод %XX из шеснадцетиричных чисел в символы
    $key =~ s/%(..)/pack("c",hex($1))/ge;
    $val =~ s/%(..)/pack("c",hex($1))/ge;

    # Сопоставление ключа и значения
    # \0 - разделитель нескольких значений
    $$in{$key} .= "\0" if (defined($$in{$key}));
    $$in{$key} .= $val;

  }
  return length($#in_second);
}

Подробнее об аргументах, переданных Perl (-wT), поговорим попозже. Мы начинаем с очистки переменных окружения $ENV и $PATH и посылаем HTML заголовок (это часть html протокола между браузером и сервером. Вы не можете ее видеть на странице, отображенной в браузере). Функция ReadParse() читает аргументы, переданные скрипту. Это можно было бы проще сделать при помощи модуля, однако так вы не увидели бы весь код. Затем, мы вставляем код примера. И в конце завершаем HTML файл.

 

Нулевой байт

Для Perl все символы одинаковы, что отлично, например, от функций C. Для Perl нулевой символ конца строки - это такой же символ как и все остальные. И что?

Давайте добавим следующий код в наш скрипт, чтобы получить showhtml.cgi  :

  # showhtml.cgi
  my $filename= $input{filename}.".html";
  print "<BODY>File : $filename<BR>";
  if (-e $filename) {
      open(FILE,"$filename") || goto form;
      print <FILE>;
  }


Функция ReadParse() получает только один аргумент: имя файла для отображения. Чтобы не допустить чтения файлов отличных от HTML некоторыми "невежливыми гостями", мы добавляем расширение ".html" в конец имени файла. Однако помните, что нулевой байт - это такой же символ, как и все остальные...

Поэтому, если наш запрос showhtml.cgi?filename=%2Fetc%2Fpasswd%00, файл будет называться my $filename = "/etc/passwd\0.html" и нашему удивленному взору предстанет кое-что не являющееся HTML.

Что случилось? Команда strace показывает, как Perl открывает файл:

  /tmp >>cat >open.pl << EOF
  > #!/usr/bin/perl
  > open(FILE, "/etc/passwd\0.html");
  > EOF
  /tmp >>chmod 0700 open.pl
  /tmp >>strace ./open.pl 2>&1 | grep open
  execve("./open.pl", ["./open.pl"], [/* 24 vars */]) = 0
  ...
  open("./open.pl", O_RDONLY)             = 3
  read(3, "#!/usr/bin/perl\n\nopen(FILE, \"/et"..., 4096) = 51
  open("/etc/passwd", O_RDONLY)           = 3

Последний open(), показанный strace, соответствует системному вызову, написанному на C. Мы можем видеть, что расширение .html исчезает, что позволяет нам открыть /etc/passwd.

Это проблема решается при помощи одного регулярного выражения, которое удаляет все нулевые байты:

s/\0//g;
 

Использование каналов

Вот скрипт, написанный без всякой защиты. Он отображает заданный файл из дерева каталогов /home/httpd/:

#pipe1.cgi

my $filename= "/home/httpd/".$input{filename};
print "<BODY>File : $filename<BR>";
open(FILE,"$filename") || goto form;
print <FILE>;

Не смейтесь с этого примера! Я видел подобные скрипты.

Первое использование его очевидно:

pipe1.cgi?filename=..%2F..%2F..%2Fetc%2Fpasswd
Нужно только поднятся вверх по дереву, чтобы получить доступ к любому файлу. Однако тут есть другая более интересная возможность: выполнить команду по своему выбору. В Perl, команда open(FILE, "/bin/ls") открывает двоичный файл "/bin/ls"... однако open(FILE, "/bin/ls |") исполняет указанную команду. Добавление одного символа канала |, изменяет поведение open().

Другая проблема возникает из-за того, что не проверяется существование файла, что позволяет нам не только выполнить любую команду, но и передать ей аргументы: pipe1.cgi?filename=..%2F..%2F..%2Fbin%2Fcat%20%2fetc%2fpasswd%20| отображает содержимое файла паролей.

Проверка на существование файла для открытия, дает меньше свободы:

#pipe2.cgi

my $filename= "/home/httpd/".$input{filename};
print "<BODY>File : $filename<BR>";
if (-e $filename) {
  open(FILE,"$filename") || goto form;
  print <FILE>
} else {
  print "-e failed: no file\n";
}

Предыдущий пример больше не работает. Проверка "-e" не проходит, так как невозможно найти файл "../../../bin/cat /etc/passwd |".

Попробуем сейчас команду /bin/ls. Поведение скрипта будет тем же. То есть, если мы попытаемся вывести список файлов в директрии /etc, "-e" проверит существование файла "../../../bin/ls /etc |", однако он также не существует. Так как мы не можем задать имя нужного файла, мы не сможем получить ничего интересного :(

Однако по-прежнему есть "выход из положения", даже если наш результат не так уж хорош. Файл /bin/ls существует (на большинстве систем), однако, если open() вызвана с этим именем файла, команда не будет выполнена, а будет выведен двоичный код. Поэтому мы должны найти способ поместить канал '|' в конец имени, но так, чтобы он не использовался при проверке "-e". Мы уже знаем решение - нулевой байт. Если мы пошлем "../../../bin/ls\0|" в качестве имени, проверка на существование пройдет успешно, так как она касается только "../../../bin/ls", однако open() может видеть канал и поэтому выполнит команду. Поэтому, URI, который нам даст содержимое текущей дериктории таков:

pipe2.cgi?filename=../../../bin/ls%00|
 

Перевод строки

Скрипт finger.cgi выполняет инструкцию finger на нашей машине:

#finger.cgi

print "<BODY>";
$login = $input{'login'};
$login =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g;
print "Login $login<BR>\n";
print "Finger<BR>\n";
$CMD= "/usr/bin/finger $login|";
open(FILE,"$CMD") || goto form;
print <FILE>

Этот скрипт по крайней мере предпринимает полезную меру предосторожности: он заботится, чтобы ненужные символы не были интерпретированы оболочкой, помещая '\' перед ними. Таким образом, точка с запятой заменяется на "\;" при помощи регулярного выражения. Однако список не содержит все интересные символы. Среди них нет перевода строки '\n'.

В командной строке вашей любимой оболочки вы активизируете инструкцию, нажимая клавишу Ввод, которая передает символ '\n'. В Perl вы можете делать то же самое. Мы уже видели, что инструкция open() позволяет нам выполнять команду, если строка оканчивается символом канала '|'.

Чтобы смоделировать такое поведение, мы добавим возврат каретки и инструкцию после имени, которое посылается команде finger:

finger.cgi?login=kmaster%0Acat%20/etc/passwd

Другие символы, которые позволяют исполнить различные инструкции в одной строке:

Эти символы не работают в данном примере, т.к. они обрабатываются регулярным выражением. Однако, найдем способ решить эту проблему.  

Бэкслэш и точка с запятой

Предыдущий скрипт finger.cgi обходит проблемы, которые могут возникнуть с использованием некоторых символов. Таким образом, URI

finger.cgi?login=kmaster;cat%20/etc/passwd

не работает, так как перед точкой с запятой ставится бэкслэш. Однако, один символ таким образом не обрабатыватся - бэкслэш '\'.

Возьмем к примеру скрипт, который не дает нам подняться по дереву каталогов при помощи регулярного выражения s/\.\.//g, которое удаляет "..". Не важно, что получится! Оболочки могут работать с несколькими подряд идущими '/' (чтобы убедиться просто попробуйте cat ///etc//////passwd).

Например, в вышеуказанном скрипте pipe2.cgi переменная $filename инициализируется при помощи префикса "/home/httpd/". Использование предыдущего регулярного выражения может показаться достаточным, чтобы не дать возможности подняться по директориям наверх. Конечно это выражение защитит от "..", однако что будет если мы поставим бэкслэш перед символом '.'? Регулярное выражение не сработает, если имя файла будет .\./.\./etc/passwd. Заметим, что с таким именем файла прекрасно работает функция system() (или ` ... `), но open() и "-e" возвращают ошибку.

Вернемся к скрипту finger.cgi. Вызов finger.cgi?login=kmaster;cat%20/etc/passwd с использованием точки с запятой не дает ожидаемого результата, так как точка с запятой теряет свое значение после работы регулярного выражения. То есть оболочка получает инструкцию:

/usr/bin/finger kmaster\;cat /etc/passwd
В логах веб-сервера появляется следующая ошибка:
finger: kmaster;cat: no such user.
finger: /etc/passwd: no such user.
Сообщения идентичны тем, что появятся, если вы введете эту команду в командной строке. Источник проблемы в том, что бэкслэш перед ';' заставляет рассматривать этот символ как часть строки "kmaster;cat".

Мы же хотим разделить две инструкции: одну для скрипта, а вторую для своего использования. Для этого мы должны заранее поставить бэкслэш перед ';': <A HREF="finger.cgi?login=kmaster\;cat%20/etc/passwd"> finger.cgi?login=kmaster\;cat%20/etc/passwd</A>. Строка "\;" заменяется скриптом на "\\;", а затем посылается оболочке. Оболочка получает команду :

/usr/bin/finger kmaster\\;cat /etc/passwd
И разбивает ее на две инструкции:
  1. /usr/bin/finger kmaster\, которая вероятнее всего ничего не даст... но она нам и не нужна :-)
  2. cat /etc/passwd - она покажет нам файл с паролями.
Решение этой проблемы простое: надо в регулярном выражении обрабатывать и сам бэкслэш '\'.

 

Использование незащищенного символа "

Иногда параметр "защищают" от нежелательного использования при помощи кавычек. Мы изменим скрипт finger.cgi так, чтобы защитить таким способом переменную $login.

Однако, если в скрипте перед кавычками не ставятся бэкслэши, этот метод не дает пользы. Пара кавычек, добавленных из вашего запроса, испортит все дело. Так получится, потому что первая кавычка из запроса закроет открывающую кавычку из скрипта. Затем, вы можете вписать команду, а вторая кавычка из запроса откроет строку, которую закроет вторая кавычка из скрипта.

Скрипт finger2.cgi иллюстрирует это:

#finger2.cgi

print "<BODY>";
$login = $input{'login'};
$login =~ s/\0//g;
$login =~ s/([<>\*\|`&\$!#\(\)\[\]\{\}:'\n])/\\$1/g;
print "Login $login<BR>\n";
print "Finger<BR>\n";
#Новая (не)эффективная супер-защита:
$CMD= "/usr/bin/finger \"$login\"|";
open(FILE,"$CMD") || goto form;
while(<FILE>) {
  print;
}

URI для выполнения берем:

finger2.cgi?login=kmaster%22%3Bcat%20%2Fetc%2Fpasswd%3B%22
Оболочка получит команду /usr/bin/finger "$login";cat /etc/passwd;"", кавычки не доставят нам никаких проблем.

Поэтому важно, если вы хотите обезопасить пареметры при помощи кавычек, ставить бэкслэши перед ними, также как и перед точкой с запятой и бэкслэшем, что мы обсуждали ранее.

 

Программирование на Perl

 

Опции warning и tainting

Программируя на Perl, используйте опцию w или "use warnings;" (Perl 5.6.0 или позже), они предупредят вас о потенциальных проблемах, таких как неинициализированные переменные и устаревшие выражения/функции.

Опция T (taint mode) предоставляет более высокий уровень безопасности. Этот режим инициирует различные проверки. Самая важная касается возможного загрязнения (tainting) переменных. Переменная может быть либо чистой, либо грязной. Данные поступившие извне программы считаются грязными до тех пор, пока они не будут очищены. Грязные переменные не могут быть использованы для передачи значений, которые будут использоваться за пределами программы (вызов команд оболочки).

В данном режиме аргументы командной строки, переменные окружения, результаты некоторых системных вызовов (readdir(), readlink(), readdir(), ...) и данные полученные из файлов считаются подозрительными, и поэтому, грязными.

Чтобы очистить переменную вы должны обработать ее регулярным выражением. Очевидно, что использование .* не даст результата. Цель такого подхода - заставить вас заботиться о предоставленных аргументах. Всегда использовать регулярные выражения для этого один из вариантов.

Тем не менее этот режим не обезопасит вас от всех проблем: загрязненность аргументов, переданых system() или exec() через переменную-список, не проверяется. Поэтому вы должны быть очень осторожны, если один из ваших скриптов использует эти функции. Инструкция exec "sh", '-c', $arg; считается безопасной вне зависимости от того грязная или нет переменная $arg :(

Также рекомендуется добавлять "use strict;" в начало своей программы. Это заставит вас объявлять переменные; некоторые люди находят это надоедливым, однако это обязательно, если вы используете mod-perl.

Таким образом CGI скрипт на Perl должен начинаться с:

#!/usr/bin/perl -wT
use strict;
use CGI;
или на Perl 5.6.0 :
#!/usr/bin/perl -T
use warnings;
use strict;
use CGI;


 

Вызов open()

Много программистов открывают файл просто используя open(FILE,"$filename") || .... Мы уже видели опасность такого кода. Чтобы уменшить риск, указывайте режим открытия:

Не открывайте свои файлы, не указывая режим.

Перед доступом к файлу рекомендуется проверить его существование. Это не защитит от проблем, связанных с условием перехвата, описаных в предыдущей статье, однако обойдет ловушки вроде команд с аргументами.

if ( -e $filename ) { ... }

Начиная с Perl 5.6 появился новый синтаксис для open() : open(ФАЙЛОВЫЙ ДЕСКРИПТОР,РЕЖИМ,СПИСОК). С режимом '<' файл открывается для чтения, с '>' - файл урезается или создается по необходимости, а затем открывается для записи. Появляются новые интересные режимы для общения с другими процессами. Если режим указан как '|-' или '-|', аргумент СПИСОК интерпретируется как команда, которая расположена после или до символа канала соответственно.

До Perl 5.6 и трехаргументной функции open() некоторые люди использовали команду sysopen().

 

Обработка и фильтрация входящих данных

Есть два метода: либо вы указываете при помощи регулярного выражения запрещенные символы, либо вы явно определяете допустимые символы. Программы из примеров должны были вас убедить, что очень легко забыть отфильтровать потенциально опасные символы, поэтому второй метод предпочтительней.

На практике вот что надо сделать: во-первых проверить, что запрос содержит только допустимые символы. Затем поставить бэкслэши перед допустимыми символами, которые являются опасными.

#!/usr/bin/perl -wT

# filtre.pl

#  Переменные $safe и $danger содержат символы, которые
#  не представляют и представляют опасность, соответственно.
#  Добавьте или удалите из них символы, чтобы изменить работу фильтра.
#  Данные в $input считаются верными, только если все символы содержатся
#  среди допустимых.

use strict;

my $input = shift;

my $safe = '\w\d';
my $danger = '&`\'\\|"*?~<>^(){}\$\n\r\[\]';
#Замечание:
#  '/', пробел и символ табуляции специально не включены в список

if ($input =~ m/^[$safe$danger]+$/g) {
    $input =~ s/([$danger]+)/\\$1/g;
} else {
    die "Недопустимые символы в $input\n";
}
print "input = [$input]\n";


Скрипт определяет два множества символов:

Любой запрос, содержащий символ, не принадлежащий обоим множествам, немедленно отвергается.

 

PHP скрипты

Не хочу выглядеть полемистом, однако я считаю, что скрипты лучше писать на PHP, чем на Perl. Точнее, как системный администратор, я предпочитаю, чтобы мои пользователи писали скрипты на PHP, а не на Perl. Если кто-либо пишет на PHP, не заботясь о безопасности, он будет так же опасен, как если бы он писал на Perl, так почему я предпочитаю PHP? Если у вас есть какие-либо проблемы в программировании на PHP, вы можете включить безопасный режим (safe_mode=on) или отключить функции (disable_functions=...). Безопасный режим запрещает доступ к файлам, не принадлежащим пользователю, запрещает изменять переменные окружения кроме специально разрешенных, выполнять команды и т.д.

По умолчанию Apache указывает нам в заголовке, что используется PHP.

$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
HEAD / HTTP/1.0

HTTP/1.1 200 OK
Date: Tue, 03 Apr 2001 11:22:41 GMT
Server: Apache/1.3.14 (Unix)  (Red-Hat/Linux) mod_ssl/2.7.1
        OpenSSL/0.9.5a PHP/4.0.4pl1 mod_perl/1.24
Connection: close
Content-Type: text/html

Connection closed by foreign host.
Впишите expose_PHP = Off в /etc/php.ini чтобы скрыть эту информацию:
Server: Apache/1.3.14 (Unix)  (Red-Hat/Linux) mod_ssl/2.7.1
OpenSSL/0.9.5a mod_perl/1.24


В файлах /etc/php.ini (PHP4) и /etc/httpd/php3.ini есть много параметров, которые могут помочь укрепить систему. Например, опция "magic_quotes_gpc" добавляет бэкслэши перед кавычкими и некоторыми другими символами в аргументах, полученных через методы GET, POST и из cookie; это решает несколько проблем, которые мы видели в наших примерах на Perl.

 

Вывод

Вероятно эта статья самая простая для понимания среди остальных статей из нашей серии. Она показывает уязвимости, которые используются каждый день в вебе. Есть много других, часто основанных на плохом программировании (например, скрипт, посылающий письмо, и получающий данные для поля From: через аргумент, предоставляет отличный плацдарм для спамеров). Примеров очень много. Как только скрипт появится на веб-сайте, будте уверены, что минимум один человек попробует использовать его в несоответствующих целях.

Эта статья завершает цикл статей о безопасном программировании. Мы надеемся, что помогли вам узнать основные дыры в безопасности, которые обнаруживаются во многих приложениях, и поэтому вы будете принимать во внимание параметр безопасности при проектировании и программировании своего приложения. Проблемами безопасности часто пренебрегают из-за ограниченной сферы применения разработки (внутреннее использование, использование в частной сети, временная модель и т.д.). Тем не менее модуль, исходно разработанный только для очень ограниченного применения, может стать базовым для более серьезного приложения, а потому изменения впоследствии будут гораздо более дорогостоящими.


 

Некоторые символы закодированные для испольования в URI

URI код (ISO 8859-1) Символ
%00 \0 (конец строки)
%0a \n (возврат каретки)
%20 пробел
%21 !
%22 "
%23 #
%26 & (амперсанд)
%2f /
%3b ;
%3c <
%3e >
Таблица 1 : соответствие ISO 8859-1 и символов

 

Ссылки


 

Программа guestbook.cgi с ошибками

#!/usr/bin/perl -w

# guestbook.cgi

BEGIN { $ENV{PATH} = '/usr/bin:/bin' }
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # Сделаем %ENV безопаснее =:-)
print "Content-type: text/html\n\n";
print "<HTML>\n<HEAD><TITLE>Дырявая гостевая книга</TITLE></HEAD>\n";
&ReadParse(\%input);
my $email= $input{email};
my $texte= $input{texte};
$texte =~ s/\n/<BR>/g;

print "<BODY><A HREF=\"guestbook.html\">
       Гостевая книга </A><BR><form action=\"$ENV{'SCRIPT_NAME'}\">\n
      Email: <input type=text name=email><BR>\n
      Текст:<BR>\n<textarea name=\"texte\" rows=15 cols=70>
      </textarea><BR><input type=submit value=\"Вперед!\">
      </form>\n";
print "</BODY>\n";
print "</HTML>";
open (FILE,">>guestbook.html") || die ("Не могу записать\n");
print FILE "Email: $email<BR>\n";
print FILE "Текст: $texte<BR>\n";
print FILE "<HR>\n";
close(FILE);
exit(0);

sub ReadParse {
  my $in =shift;
  my ($i, $key, $val);
  my $in_first;
  my @in_second;

  # Изучим данные
  if ($ENV{'REQUEST_METHOD'} eq "GET") {
    $in_first = $ENV{'QUERY_STRING'};
  } elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
    read(STDIN,$in_first,$ENV{'CONTENT_LENGTH'});
  }else{
    die "ОШИБКА: неизвестный метод запроса\n";
  }

  @in_second = split(/&/,$in_first);

  foreach $i (0 .. $#in_second) {
    # Заменяем плюсы на пробелы
    $in_second[$i] =~ s/\+/ /g;

    # Разбиваем на ключ и значение
    ($key, $val) = split(/=/,$in_second[$i],2);

    # Переконвертируем %XX из шеснадцатиричных чисел в символы
    $key =~ s/%(..)/pack("c",hex($1))/ge;
    $val =~ s/%(..)/pack("c",hex($1))/ge;

    # Ассоциируем ключ и значение
    $$in{$key} .= "\0" if (defined($$in{$key}));
    $$in{$key} .= $val;

  }

  return length($#in_second);
}


 

Страница отзывов

У каждой заметки есть страница отзывов. На этой странице вы можете оставить свой комментарий или просмотреть комментарии других читателей :
 talkback page 

Webpages maintained by the LinuxFocus Editor team
© Frйdйric Raynal, Christophe Blaess, Christophe Grenier, FDL
LinuxFocus.org
Translation information:
fr --> -- : Frйdйric Raynal, Christophe Blaess, Christophe Grenier <pappy(_at_)users.sourceforge.net, ccb(_at_)club-internet.fr, grenier(_at_)nef.esiea.fr>
fr --> en: Georges Tarbouriech <georges.t(_at_)linuxfocus.org>
en --> en: Lorne Bailey <sherm_pbody(_at_)yahoo.com>
en --> ru: Kolobynin Alexey <alexey_ak0(_at_)mail.ru>

2002-09-26, generated by lfparser version 2.31