|
|
автор 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> Содержание:
|
Резюме:
Получить исполняемый файл, который представляет собой плохо написанный Perl скрипт ...
"Существует Более Одного Способа Это Сделать!"
Предыдущие статьи из серии:
Когда клиент запрашивает 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
www.linuxdoc.org
;
/cgi-bin/ldpsrch.cgi
;
?
- начало длинного списка аргументов:
srv=http%3A%2F%2Fwww.linuxdoc.org
- это сервер, с которого поступил
запрос;
srch=ssh
содержит сам запрос, что искать;
db=1
означает, что поиск ведется только в HOWTO;
scope=0
означает, что поиск ведется в содержимом документа, а не
только в его заголовке;
rpt=20
ограничивает до 20 количество найденных документов, отображаемых
на странице.
Часто имена аргументов и их значения, дают представление для чего они предназначены. Более того, содержимое страницы с ответом еще лучше поясняет их назначение.
Теперь вы знаете, что лучшая сторона CGI скриптов - возможность пользователя передавать данные через аргументы... но худшая сторона - то что плохо написанный скрипт открывает дыру в безопасности.
Вы наверное заметили странные символы, которые использует ваш любимый браузер
в предыдущем запросе. Эти символы закодированы в символьном наборе ISO 8859-1
(взгляните на >man iso_8859_1
).
В таблице 1
представлены значения некоторых из этих кодов. Вспомним, что в серверах IIS4.0 и IIS5.0
существует весьма опасная уязвимость, называемая unicode bug, основанная на
представлении символов "/" и "\" в unicode.
SSI Server Side Include
"Server Side Include - вставки на стороне сервера
- одна
из возможностей веб-сервера. Она позволяет вставлять инструкции в веб-страницы, такие как
включение в страницу необработанного файла или выполнение команды (оболочки или CGI скрипта).
В файле конфигурации Apache httpd.conf
инструкция
"AddHandler server-parsed .shtml
" включает этот механизм.
Часто, чтобы убрать различие между .html
и .shtml
,
в этой инструкции используют расширение .html
. Естественно, это
замедляет работу сервера... Работа механизма контролируется на уровне директорий
при помощи инструкций:
Options Includes
активизирует все SSI ;
OptionsIncludesNoExec
запрещает использование exec cmd
и
exec cgi
.
В присоединенном к этому документу скрипте
guestbook.cgi
,
текст, предоставленный пользователем, включается в HTML файл без замены
символов '<' и ' >' на HTML код < и >.
Любознательный человек может передать одну из следующих инструкций:
<!--#printenv -->
(обратите внимение на пробел после printenv
)
<!--#exec cmd="cat /etc/passwd"-->
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.
В этом параграфе мы рассмотрим дыры в безопасности, относящиеся к 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
Другие символы, которые позволяют исполнить различные инструкции в одной строке:
;
: после окончания первой инструкции, выполняется следующая;
&&
: если результат выполнения первой инструкции
успешен (т.е. она возвратила 0 оболочке), то выполняется следующая;
||
: при ошибочном выполнении первой инструкции (т.е.
она возвратила ненулевое значение оболочке), выполняется следующая.
Предыдущий скрипт 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И разбивает ее на две инструкции:
/usr/bin/finger kmaster\
, которая вероятнее всего ничего не даст... но она
нам и не нужна :-)
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, используйте опцию 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") ||
...
. Мы уже видели опасность такого кода. Чтобы уменшить риск, указывайте
режим открытия:
open(FILE,"<$filename") || ...
только для чтения;
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";
Скрипт определяет два множества символов:
$safe
содержит те, которые не представляют опасности (здесь только цифры и буквы);
$danger
содержит символы, которые допустимы, но потенциально опасны.
Не хочу выглядеть полемистом, однако я считаю, что скрипты лучше писать на 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 код (ISO 8859-1) | Символ |
%00 | \0 (конец строки) |
%0a | \n (возврат каретки) |
%20 | пробел |
%21 | ! |
%22 | " |
%23 | # |
%26 | & (амперсанд) |
%2f | / |
%3b | ; |
%3c | < |
%3e | > |
man perlsec
: страница man языка Perl о безопасности;
#!/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); }
|
Webpages maintained by the LinuxFocus Editor team
© Frйdйric Raynal, Christophe Blaess, Christophe Grenier, FDL LinuxFocus.org |
Translation information:
|
2002-09-26, generated by lfparser version 2.31