HTML page
Программирование CGI
Введение
Резкие изменения окружающей среды приводят к тому, что некоторые виды лучше других добывают пропитание или избегают хищников. Многие ученые полагают, что миллионы лет назад при столкновении кометы с Землей в атмосферу поднялось огромное облако пыли. За этим последовали радикальные изменения окружающей среды. Некоторые организмы - например, динозавры - не смогли справиться с ними, что привело к их вымиранию. Другие виды (в частности, млекопитающие) нашли новые источники пищи и места обитания и продолжили борьбу за существование.Подобно тому, как комета изменила среду обитания доисторических животных, развитие Web изменило ситуацию в современных языках программирования и открыло новые горизонты. Хотя некоторые языки так и не прижились в "новом мировом порядке", Perl выдержал испытание. Благодаря своим сильным сторонам - обработке текстов и объединению системных компонентов - Perl легко приспособился к задачам пересылки информации с использованием текстовых протоколов.
Архитектура
В основе Web лежит обычный текст. Web-серверы общаются с броузерами с помощью текстового протокола HTTP (Hypertext Transfer Protocol). Многие пересылаемые документы кодируются специальной разметкой, которая называется HTML (Hypertext Markup Language). Текстовая ориентация внесла немалый вклад в гибкость, широту возможностей и успех Web.Единственным исключением на этом фоне является протокол SSL (Secure Socket Layer) - он
шифрует другие протоколы (например, HTTP) в двоичные данные, защищенные от перехват. Web-страницы идентифицируются по так называемым URL (Universal Resource Locator). URL выглядят так:
http://www.perl.com/CPAN/ http://www.perl.com:8001/bad/mojo.html
ftp://gatekeeper.dec.com/pub/misc/netlib.tar.Z
ftp://anonymous@myplace:gatekeeper.dec.co7n/pub/misc/netlib.tar.Z file:///etc/motd
Первая часть (http, ftp, file) называется схемой и определяет способ получения файла. Вторая (://) означает, что далее следует имя хоста, интерпретация которого зависит от схемы. За именем хоста следует путь, идентифицирующий документ. Путь также называется частичным URL.
Web является системой "клиент/сервер". Клиентские броузеры (например, Netscape или Lynx) запрашивают документы (идентифицируемые по частичным URL) у Web-серверов - таких, как Apache. Диалог броузера с сервером определяется протоколом HTTP. В основном сервер просто пересылает содержимое некоторого файла. Однако иногда Web-сервер запускает другую программу для отправки документа, который может представлять собой HTML-текст, графическое изображение или иной тип данных. Диалог сервера с программой определяется протоколом CGI (Common Gateway Interface), а запускаемая сервером программа называется программой CGI или сценарием CGI. Сервер сообщает программе CGI, какая страница была затребована, какие значения были переданы в HTML-формах, откуда поступил запрос, какие данные использовались при аутентификации и многое другое. Ответ программы CGI состоит из двух частей: заголовка, говорящего "Я передаю документ HTML", "Я передаю изображение формата GIF" или "Я вообще ничего не передаю, обращайся на такую-то страницу", и тела документа (возможно, содержащего данные GIF, обычный текст или код HTML).
Правильно реализовать протокол CGI нелегко, а ошибиться проще простого, поэтому мы рекомендуем использовать превосходный модуль CGI.pm Линкольна Штейна (Lincoln Stein). Модуль содержит удобные функции для обработки информации, полученной от сервера, и подготовки ответов CGI, ожидаемых сервером. Это чрезвычайно полезный модуль был включен в стандартную поставку Perl версии 5.004 вместе с вспомогательными модулями (например,
CGI::Carp или CGI::Fast). Использование модуля демонстрируется в рецепте 19.1.
Некоторые Web-серверы содержат встроенный интерпретатор Perl, что позволяет генерировать документы на Perl без запуска нового процесса. Системные издержки на чтение неизменившейся страницы пренебрежимо малы для страниц с редкими обращениями (даже порядка нескольких обращений в секунду). Однако вызовы CGI существенно замедляют компьютер, на котором работает Web-сервер. В рецепте 19.5 показано, как работать с mod_perl, встроенным интерпретатором Perl Web-сервера Apache, чтобы пользоваться преимуществами программ CGI без издержек, связанных с ними.
За кулисами
Программы CGI вызываются каждый раз, когда Web-серверу требуется сгенерировать динамический документ. Необходимо понимать, что программа CGI не работает постоянно с обращениями к ее различным частям со стороны броу-зера. При каждом запросе частичного URL, соответствующего программе, запускается ее новая копня. Программа генерирует страницу для данного запроса и завершается.Броузер может запросить документ несколькими способами, которые называются методами (не путайте методы HTTP с методами объектно-ориентированного программирования!). Чаще всего встречается метод GET, который обозначает простой запрос документа. Метод HEAD используется в том случае, если броузер хочет получить сведения о документе без фактической загрузки. Метод POST применяется при передаче заполненных форм.
Значения полей форм также могут кодироваться в методах GET и POST. В методе GET значение кодируется прямо в URL, что приводит к появлению уродливых URL следующего вида:
http://mox.perl.com/cgi-bin/program?name=JohannG'born=1685
В методе POST значения находятся в другой части запроса HTTP - не той, которую броузер отправляет серверу. Если бы в приведенном выше URL значения нолей отсылались методом POST, то пользователь, сервер и сценарий CGI видели бы обычный URL:
http://mox.perl.com/cgi -bin/program
Методы GET и POST также отличаются свойством идемпотентности. Проще говоря, однократный или многократный запрос GET для некоторого URL должен давать одинаковые результаты. Это объясняется тем, что в соответствии со спецификацией протокола HTTP запрос GET может кэшироваться броузером, сервером или промежуточным прокси-сервером. Запросы POST не могут кэшироваться, поскольку каждый запрос считается самостоятельным и независимым от других. Как правило, запросы POST влияют на состояние сервера или зависят от него (обращение или обновление базы данных, отправка почты).
Большинство серверов регистрирует запросы к файлам (ведут журнал обращений) для их последующего анализа Web-мастером. Ошибки в программах CGI тоже по умолчанию не передаются броузеру. Вместо этого они регистрируются в файле (журнал ошибок), а броузер просто получает сообщение "500 Server Error", которое означает, что программа CGI не справилась со своей задачей.
Сообщения об ошибках полезны в процессе отладки любой программы, но особенно полезны они в сценариях CGI. Однако авторы программ CGI не всегда имеют доступ к журналу ошибок или не знают, где он находится. Перенаправление ошибок рассматривается в рецепте 19.2, а исправление - в рецепте 19.3.
В рецепте 19.9 показано, как узнать, что в действительности говорят друг другу броузер с сервером. К сожалению, некоторые броузеры не реализуют спецификацию HTTP в полной мере. Рецепт поможет выяснить, что является причиной возникших трудностей - программа или броузер.
Безопасность
Сценарии CGI позволяют запускать программы на вашем компьютере кому угодно. Конечно, программу выбираете вы, но анонимный пользователь может передать ей неожиданные значения и обмануть ее, заставляя сделать нечто нехорошее. Безопасности в Web уделяется большое внимание.Некоторые узлы решают проблему, попросту отказываясь от программ CGI. Там, где без силы и возможностей программ CGI не обойтись, приходится искать средства обезопасить их. В рецепте 19.4 приведен список рекомендаций по написанию безопасных сценариев CGI, а также кратко рассмотрен механизм пометки, защищающий от случайного применения ненадежных данных. В рецепте 19.6 показано, как организовать безопасный запуск других программ из сценария CGI.
HTML и формы
Теги HTML позволяют создавать экранные формы. В этих формах пользователь вводит значения, передаваемые серверу. Формы состоят из элементов (widgets) - например, текстовых полей и флажков. Программы CGI обычно возвращают HTML-код, поэтому в модуле CGI предусмотрены вспомогательные функции создания HTML-кода для чего угодно, от таблиц до элементов форм. В дополнение к рецепту 19.7 в этой главе также имеется рецепт 19.11. В нем показано, как создать форму, сохраняющую свои значения между вызовами. В рецепте 19.12 продемонстрировано создание одного сценария CGI, который создает и обрабатывает целый набор страниц - например, в системе приема заказов по каталогу.Ресурсы Web
Разумеется, лучшую информацию о программировании Web можно найти непосредственно в Web. Безопасность Webhttp://www.w3.org/Security/Faq/
Общие сведения о Web
http://www.boutell.com/faq/
CGI
http://www.webthing.com/tutorials/cgifaq.html
Спецификация HTTP
http://www.w3.org/pub/WWW/Protocols/HTTP/
Спецификация HTML
http://www.w3.org/TR/REC-html40/ http://www.w3.org/pub/WWW/MarkUp/
Спецификация CGI
http://www.w3.org/CGI/
Безопасность CGI
http://www.go2net.com/people/paulp/cgi-security/safe-cgi.txt
19.1. Написание сценария CGI
Проблема
Требуется написать сценарии CGI для обработки содержимого HTML-формы. В частности, вы хотите работать со значениями полей формы и выдавать нужные выходные данные.Решение
Сценарий CGI представляет собой программу, работающую на сервере и запускаемую Web-сервером для построения динамического документа. Он получает кодированную информацию от удаленного клиента (пользовательского броузера) через STDIN и неременные окружения и выводит в STDOUT правильные заголовки и тело запросов HTTP. Стандартный модуль CGI (см. пример 19.1) обеспечивает удобное преобразование ввода и вывода.Пример 19.1. hiweb
#!/usr/bin/perl -w
# hiweb - загрузить модуль CGI для расшифровки # данных, полученных от Web-сервера use
strict;
use CGI qw(:standard escapeHTML);
# Получить параметр от формы my $value = param('PARAM_NAME');
# Вывести документ print header(), start_html("Howdy there!"),
p("You typed: ", #(escapeHTML($value))),
end_html();
Комментарий
CGI - всего лишь протокол, формальное соглашение между Web-сервером и отдельной программой. Сервер кодирует входные данные клиентской формы, а программа CGI декодирует форму и генерирует выходные данные. В специфшка-ции протокола ничего не сказано о языке, на котором должна быть написана программа. Программы и сценарии, соответствующие протоколу CGI, могут быть написаны в командном интерпретаторе, на С, Rexx, C++, VMS DCL, Smalltalk, Tel, Python и, конечно, на Perl.Полная спецификация CGI определяет, какие данные хранятся в тех или иных переменных окружения (например, входные параметры форм) и как они кодируются. Теоретически декодирование входных данных в соответствии с протоколом не должно вызывать никаких проблем, но на практике задача оказывается на удивление хитрой. Именно поэтому мы настоятельно рекомендуем использовать превосходный модуль CGI Линкольна Штейпа. Вся тяжелая работа по иравильной обработке требований CGI выполнена заранее; вам остается лишь написать содержательную часть программы без нудных сетевых протоколов.
Сценарии CGI вызываются двумя основными способами, которые называются методами, - но не путайте методы HTTP с методами объектов Perl! Метод GET используется для получения документов в ситуациях, когда идентичные запросы должны давать идентичные результаты - например, при поиске в словаре. В методе GET данные формы хранятся внутри URL. Это позволяет сохранить запрос на будущее, но ограничивает общий размер запрашиваемых данных. Метод POST отправляет данные отдельно от запроса. Он не имеет ограничений на размер, но не может сохраняться. Формы, обновляющие информацию на сервере (например, при отправке ответного сообщения или модификации базы данных), должны использовать POST. Клиентские броузеры и промежуточные прокси-серверы могут кэшировать и обновлять результаты запросов GET незаметно для пользователя, но запросы POST не кэшируются. Метод GET надежен лишь для коротких запросов, ограничивающихся чтением информации, тогда как метод POST надежно работает для форм любого размера, а также подходит для обновления и ответов в схемах с обратной связью. По умолчанию модуль CGI использует POST для всех форм.
За небольшими исключениями, связанными с правами доступа к файлам и режимами повышенной интерактивности, сценарии CGI делают практически все то же, что и любая другая программа. Они могут возвращать результаты во многих форматах: обычный текст, документы HTML, звуковые файлы, графика и т. д. в зависимости от заголовка HTTP. Помимо вывода простого текста или HTML-кода, они также могут перенаправлять клиентский броузер в другое место, задавать серверные cookies, требовать аутентификации или сообщать об ошибках.
Модуль CGI поддерживает два интерфейса - процедурный для повседневного использования и объектно-ориентированный для компетентных пользователей с нетривиальными потребностями. Практически все сценарии CGI должны использовать процедурный интерфейс, но, к сожалению, в большей части документации по CGI.pm приведены примеры для исходного объектно-ориентированного подхода. Есди вы хотите использовать упрощенный процедурный интерфейс, то для обеспечения обратной совместимости вам придется явно запросить его с помощью тега : standard. О тегах рассказано в главе 12 "Пакеты, библиотеки и модули". Чтобы прочитать входные данные пользовательской формы, передайте функции param имя нужного поля. Если на форме имеется поле с именем "favorite", то вызов param( "favorite") вернет его значение. В некоторых элементах форм (на-, пример, в списках) пользователь может выбрать несколько значений. В таких случаях param возвращает список значений, который можно присвоить массиву.
Например, следующий сценарий получает значения трех полей формы, последнее из которых может возвращать несколько значений:
use CGI qw(:standard);
$who = param("Name");
$phone = paramC'Number");
@picks = param("Choices");
При вызове без аргументов рагат возвращает список допустимых параметров формы в списковом контексте или количество параметров формы в скалярном контексте. Вот и все, что нужно знать о пользовательском вводе. Делайте с ним, что хотите, а потом генерируйте выходные данные в нужном формате. В этом тоже нет ничего сложного. Помните, что, в отличие от обычных программ, выходные данные сценария CGI должны форматироваться определенным образом: сначала набор заголовков, за ними - пустая строка и лишь потом нормальный вывод.
Как видно из решения, модуль CGI упрощает не только ввод, но и вывод данных. Он содержит функции для генерации заголовков HTTP и HTML-кода. Функция header строит текст заголовка. По умолчанию она генерирует заголовки для документов text/html, но вы можете изменить тип содержимого и передать другие необязательные параметры: print header( -TYPE => 'text/plain', -EXPIRES => -+3d' ); Модуль CGI.pm также применяется для генерации HTML-кода. Звучит тривиально, но модуль CGI проявляется во всем блеске при создании динамических форм с сохранением состояния (например, страниц, предназначенных для оформления заказов). В модуле CGI даже имеются функции для генерации форм и таблиц. При выводе элементов формы символы &, <, > и " в выходных данных HTML автоматически заменяются своими эквивалентами. В пользовательских выходных данных этого не происходит. Именно поэтому в решении импортируется и используется функция escapeHTML - даже если пользователь введет специальные символы, это не вызовет ошибок форматирования в HTML. Полный список функций вместе с правилами вызова приведен в документации но модулю CGI.pm, хранящейся в формате POD внутри самого модуля.
> Смотри также --------------------------------
Документация по стандартному модулю CGI; http://www.w3.org/CGI/', рецепт 19.7.
19.2. Перенаправление сообщений об ошибках
Проблема
У вас возникли трудности с отслеживанием предупреждений и ошибок вашего сценария, или вывод в STDERR из сценария приводит сервер в замешательство.Решение
Воспользуйтесь модулем CGI::Carp из стандартной поставки Perl, чтобы все сообщения, направляемые в STDERR, снабжались префиксом - именем приложения и текущей датой. При желании предупреждения и ошибки также можно сохранять в файле или передавать броузеру.Комментарий
Задача отслеживания сообщений в сценариях CGI пользуется дурной славой. Даже если вам удалось найти на сервере журнал ошибок, вы все равно не сможете определить, когда и от какого сценария поступило то или иное сообщение. Некоторые недружелюбные Web-серверы даже прерывают работу сценария, если он неосторожно выдал в STDERR какие-нибудь данные до генерации заголовка Content-Type - а это означает, что флаг -w может навлечь беду. На сцене появляется модуль CGI::Carp. Он замещает warn и die, а также функции carp, croak и confess обычного модуля Carp более надежными и содержательными версиями. При этом данные по-прежнему отсылаются в журнал ошибок сервера.use CGI::Сагр;
warn "This is a complaint";
die "But this one is serious";
В следующем примере использования CGI::Carp ошибки перенаправляются в файл по вашему выбору. Все это происходит в блоке BEGIN, что позволяет перехватывать предупреждения на стадии компиляции:
BEGIN {
use CGI::Carp qw(carpout);
open( LOG, ""/var/local/cgi-logs/mycgi-log")
or die "Unable to append to mycgi-log: $!\n";
carpout(*LOG);
}
Фатальные ошибки могут даже возвращаться клиентскому броузеру - это удобно при отладке, но может смутить рядового пользователя.
use CGI::Carp qw(fatalsToBrowser);
die "Bad error here";
Даже если ошибка произойдет до вывода заголовка HTTP, модуль попытается избежать ужасной ошибки 500 Server Error. Нормальные предупреждения по-прежнему направляются в журнал ошибок сервера (или туда, куда вы отправили их функцией carpout) с префиксом из имени приложения и текущего времени.
> Смотри также -------------------------------
Документация по стандартному модулю CGI::Carp; описание BEGIN в рецепте 12.3.
19.3. Исправление ошибки 500 Server Error
Проблема
Сценарий CGI выдает ошибку 500 Server Error.Решение
Воспользуйтесь приведенным ниже списком рекомендаций. Советы ориентированы на аудиторию UNIX, однако общие принципы относятся ко всем системам.Комментарий
Убедитесь, что сценарий может выполняться Web-сервером. Проверьте владельца и права доступа командой Is -/. Чтобы сценарий мог выполняться сервером, для него должны быть установлены необходимые права чтения и исполнения. Сценарий должен быть доступен для чтения и исполнения для всех пользователей (или по крайней мере для того, под чьим именем сервер выполняет сценарии). Используйте команду chmod 0755 имя-сценария, если сценарий принадлежит вам, или chmod 0555 имя-сценария, если он принадлежит анонимному пользователю Web, а вы работаете как этот или привилегированный пользователь. Бит исполнения также должен быть установлен для всех каталогов, входящих в путь.Проследите, чтобы сценарий идентифицировался Web-сервером как сценарий. Большинство Web-серверов имеет общий для всей системы каталог cgi-bin, и все файлы данного каталога считаются сценариями. Некоторые серверы идентифицируют сценарий CGI как файл с определенным расширением - например, .cgi или .рсх. Параметры некоторых серверов разрешают доступ только методом GET, а не методом POST, который, вероятно, используется вашей формой. Обращайтесь к документации по Web-серверу, конфигурационным файлам, Web-мастеру и (если ничего не помогает) в службу технической поддержки.
Если вы работаете в UNIX, проверьте, правильно ли задан путь к исполняемому файлу Perl в строке #!. Она должна быть первой в сценарии, перед ней даже не разрешаются пустые строки. Некоторые операционные системы устанавливают смехотворно низкие ограничения на размер этой строки - в таких случаях следует использовать ссылки (допустим, из /home/richh/perl на
/opt/installed/third-party/ software/perl-5.004/bin/perl - взят вымышленный патологический пример).
Если вы работаете в Win32, посмотрите, правильно ли связаны свои сценарии с исполняемым файлом Perl.
Проверьте наличие необходимых прав у сценария Проверьте пользователя, с правами которого работает сценарий, с помощью простого фрагмента из примера 19.2.
Пример 19.2. webwhoami
#!/usr/bin/perl
# webwhoami - show web users id
print "Content-Type: text/plain\n\n";
print "Running as ", scalar getpwuid($>), "\n";
Сценарий выводит имя пользователя, с правами которого он работает. Определите ресурсы, к которым обращается сценарий. Составьте список файлов, сетевых соединений, системных функций и т. д., требующих особых привилегий. Затем убедитесь в их доступности для пользователя, с правами которого работает сценарий. Действуют ли дисковые или сетевые квоты? Обеспечивает .ж защита файла доступ к нему? Не пытаетесь ли вы получить зашифрованный пароль с помощью getpwent в системе со скрытыми паролями (обычно скрытые пароли доступны только для привилегированного пользователя)?
Для всех файлов, в которые сценарий выполняет запись, установите права доступа 0666, а еще лучше - 0644, если они принадлежат тому пользователю, с чьими правами выполняется сценарий. Если сценарий создает новые файлы или перемещает/удаляет старые, потребуются также права записи и исполнения для каталога с ними. Не содержит ли сценарий ошибок Perl? Попытайтесь запустить его в командной строке. Модуль CGI.pm позволяет запускать и отлаживать сценарии в командной строке или из стандартного ввода. В следующем фрагменте "О - вводимый вами признак конца файла:
% perl -we cgi-script " Простая компиляция
% perl -w cgi-script # Параметры из stdin
(offline mode: enter name=value pairs on standard input)
nanie=joe
number=10
"D
% perl -w cgi-script name=_joe numbered # Запустить с входными
# данными формы % perl -d cgi-script name=joe number=10 # to же в отладчике
# Сценарий с методом POST в csh
% (setenv HTTP_METHOD POST; perl -w cgi-script name=joe number=10)
# Сценарий с методом POST в sh
% HTTP_METHOD=post perl -w cgi-script name=joe number=10
Проверьте журнал ошибок сервера. Большинство Web-серверов перенаправляет поток STDERR для процессов CGI в файл. Найдите его (попробуйте/usr/local/ etc/httpd/logs/error_log,
/usr/local/voww/logs/error _log или спросите у администратора) и посмотрите, есть ли в нем предупреждения или сообщения об ошибках. Не устарела ли ваша версия Perl? Ответ даст команда perl -v. Если у вас не установлена версия 5.004 или выше, вам или вашему администратору следует подумать об обновлении, поскольку 5.003 и более ранние версии не были защищены от переполнения буфера, из-за чего возникали серьезные проблемы безопасности.
Не используете ли вы старые версии библиотек? Выполните команду grep -i version для библиотечного файла (вероятно, находящегося в /usr/hb/perl5/, /usr/
local/lib/perl5/,/usr/lib/perl5/site_perl или похожем каталоге). Для CGI.pm (а фактически - для любого модуля) версию можно узнать и другим способом:
% perl -MCGI -le 'print CGI->VERSION' 2.40 Используете ли вы последнюю версию Web-сервера? Хотя такое происходит редко, но все же в Web-серверах иногда встречаются ошибки, мешающие работе сценариев. Используете ли вы флаг -w? С этим флагом Perl начинает жаловаться на неинициализированные переменные, чтение из манипулятора, предназначенного только для записи, и т. д.
Используете ли вы флаг -Т? Если Perl жалуется на небезопасные действия, возможно, вы допустили какие-то неверные предположения относительно входных данных и рабочей среды вашего сценария. Обеспечьте чистоту данных, и вы сможете спокойно спать по ночам, а заодно и получите рабочий сценарий (меченые данные и их последствия для программ рассматриваются в рецепте 19.4 и на странице руководства perlsec; в списке FAQ но безопасности CGI описаны конкретные проблемы, которых следует избегать).
Используете ли вы директиву use st rict? Она заставляет объявлять переменные перед использованием и ограничивать кавычками строки, чтобы избежать возможной путаницы с подпрограммами, и при этом находит множество ошибок.
Проверяете ли вы возвращаемые значения всех системных функций? Многие люди наивно полагают, что любой вызов open, system, rename или unlink всегда проходит успешно. Они возвращают значение, по которому можно проверить результат их работы, - так проверьте! Находит ли Perl используемые вами библиотеки? Напишите маленький сценарий, который просто выводит содержимое @INC (список каталогов, в которых ищутся модули и библиотеки). Проверьте права доступа к библиотекам (должно быть разрешено чтение для пользователя, с правами которого работает сценарий). Не пытайтесь копировать модули с одного компьютера на другой - многие из них имеют скомпилированные и автоматически загружаемые компоненты, находящиеся за пределами библиотечного каталога Perl. Установите их с нуля.
Выдает ли Perl предупреждения или сообщения об ошибках? Попробуйте использовать CGI::Carp (см. рецепт 19.2), чтобы направить предупреждения и ошибки в броузер или доступный файл.
Соблюдает ли сценарий протокол CGI? Перед возвращаемым текстом или изображением должен находиться заголовок HTTP. He забывайте о пустой строке между заголовком и телом сообщения. Кроме того, STDOUT в отличие от STDERR не очищается автоматически. Если ваш сценарий направляет в STDERR предупреждения или ошибки, Web-сервер может увидеть их раньше, чем заголовок HTTP, и па некоторых серверах это приводит к ошибке. Чтобы обеспечить автоматическую очистку STDOUT, вставьте в начало сценария следующую команду (после строки #!):
$|=1;
Никогда не пытайтесь декодировать поступающие данные формы, самостоятельно анализируя окружение и стандартный ввод - возникает слишком много возможностей для ошибок. Воспользуйтесь модулем CGI и проводите время за творческим программированием или чтением Usenet, вместо того чтобы возиться с поиском ошибок в доморощенной реализации мудреного протокола.
Справочная информация
Обратитесь к спискам FAQ и другим документам, перечисленным в конце введения этой главы. Возможно, вы допустили какую-нибудь распространенную ошибку для своей системы - прочитайте соответствующий FAQ, и вам не придется краснеть за вопросы тина: "Почему моя машина не ездит без бензина и масла?"
Спросите других. Почти у каждого есть знакомый специалист, к которому можно обратиться за помощью. Вероятно, ответ найдется намного быстрее, чем при обращении в Сеть. Если ваш вопрос относится к сценариям CGI (модуль CGI, декодирование cookies, получение данных о пользователе и т. д.), пишите в сотр.in fosy stems. www.authoring, misc,
> Смотри также ------------------------------
Рецепт 19.2; сведения о буферизации во введении к главе 8 "Содержимое файлов"; CGI FAQno адресу http://www.webthing.com/tutorials/cgifacf.html.
19.4. Написание безопасных программ CGI
Проблема
Поскольку сценарий CGI позволяет внешнему пользователю запускать программы на недоступном для него компьютере, любая программа CGI представляет потенциальную угрозу для безопасности. Вам хотелось бы свести эту угрозу к минимуму.Решение
- Воспользуйтесь режимом пометки (флаг -Т в строке #!).
- Не снимайте пометку с данных (см. ниже).
- Проверяйте все, в том числе возвращаемые значения всех элементов формы, даже скрытые элементы и значения, сгенерированные кодом JavaScript. Многие наивно полагают - раз они приказали JavaScript проверить значения полей формы перед отправкой данных, то значения действительно будут проверены. Ничего подобного! Пользователь может тривиально обойти ограничения - запретить JavaScript в своем броузере, загрузить форму и модифицировать JavaScript или общаться на уровне HTTP без броузера (см. главу 20 "Автоматизация в Web").
- Проверяйте значения, возвращаемые системными функциями.
- Помните о возможности перехвата (см. ниже).
- Используйте флаг -w и директиву use strict, чтобы застраховаться от неправильных допущений со стороны Perl. o Никогда не запускайте сценарий со сменой прав, если только это не вызвано абсолютной необходимостью. Подумайте, не будет ли достаточно сменить идентификтор группы. Любой ценой избегайте запуска с правами администратора. Если вам приходится использовать setuid или setgid, используйте командный интерпретатор, если только на вашей машине нельзя безопасно запускать сценарии Perl с setuid и вы точно знаете, что это такое.
- Всегда шифруйте пароли, номера кредитных карт, номера социального страхования и все остальное, что обычно не печатается на первых страницах местных газет. При работе с такими данными следует использовать безопасный протокол SSL.
Комментарий
Многие из этих рекомендаций подходят для любых программ - флаг -w и проверка значений, возвращаемых системными функциями, пригодятся и в тех ситуациях, когда безопасность не является первоочередной заботой. Флаг -w заставляет Perl выводить предупреждения о сомнительных конструкциях (например, когда неопределенная переменная используется так, словно ей присвоено законное значение, или при попытке записи в манипулятор, доступный только для чтения).Самая распространенная угроза безопасности (не считая непредвиденных вызовов командного интерпретатора) кроется в передаче форм. Кто угодно может сохранить исходный текст формы, отредактировать HTML-код и передать измененную форму. Даже если вы уверены, что поле может возвращать только "yes" или "по", его всегда можно отредактировать и заставить возвращать "maybe". Даже скрытые поля, имеющие тип HIDDEN, не защищены от вмешательства извне. Если программа на другом конце слепо полагается на значения полей, ее можно заставить удалять файлы, создавать новые учетные записи пользователей, выводить информацию из баз данных паролей или кредитных карт и совершать множество других злонамеренных действий. Вот почему нельзя слепо доверять данным (например, информации о цене товара), хранящимся в скрытых полях при написании приложений CGI для электронных магазинов.
Еще хуже, если сценарий CGI использует значение поля формы как основу для выбора открываемого файла или выполняемой команды. Ложные значения, переданные сценарию, заставят его открывать произвольные файлы. Именно из-за таких ситуаций в Perl появился режим помеченных данных. Если программа выполняет set u id или имеет активный флаг -Г, то любые данные, получаемые ею в виде аргументов, переменных окружения, списков каталогов или файлов, считаются ненадежными и не могут прямо или косвенно воздействовать на внешний мир. В этом режиме Perl настаивает на том, чтобы переменная пути задавалась заранее, даже если при запуске программы указывается полный путь. Дело в том, что нельзя быть уверенным, что выполняемая команда не вызовет другую программу по относительному имени. Кроме того, вы должны снимать пометку со всех внешних данных. Например, при выполнении в режиме пометки фрагмента:
#!/usr/bin/perl -Т
open(FH, oo> $ARGV[0]") or die;
Perl выдает следующее предупреждение:
Insecure dependency in open while running with -T switch at ... Это объясняется тем, что значение $ARGV[0] (поступившее в программу извне) считается не заслуживающим доверия. Единственный способ снять пометку с ненадежных данных - использовать обратные ссылки в регулярных выражениях:
$file = $argv[0]; # $file помечена
unless ($file =~ mft"([\w.-]+)$") { # С $1 снята пометка
die "filename '$file' has invalid characters.\n";
} $file = $1; # С $file снята пометка
Помеченные данные могут поступать из любого источника, находящегося вне программы, - например, из аргументов или переменных окружения, из результатов чтения файловых или каталоговых манипуляторов, команды stat или данных о локальном контексте. К числу операций, которые считаются ненадежными с помеченными данными, относятся: system(CTPOKA), exec(CTPOKA), '...', glob, open в любом режиме, кроме "только для чтения", unlink, mkdi r, rmdir, chown, chmod, umask, link, symlink, флаг командной строки -s, kill, require, eval, truncate, iocti, fcnti, socket,socketpair,bind,connect,chdir, chroot, setgrp, setpriority и syscall.
Один из распространенных видов атаки связан с так называемой ситуацией перехвата (race condition). Ситуация перехвата возникает тогда, когда нападающий вмешивается между двумя вашими действиями и вносит какие-то изменения, нарушающие работу программу. Печально известная ситуация перехвата возникала при работе setuid-сценариев в старых ядрах UNIX. Между тем как ядро читало файл и выбирало нужный интерпретатор и чтением файла интерпретатором после setuid злонамеренный чужак мог подставить свой собственный сценарий. Ситуации перехвата возникают даже во внешне безобидных местах. Допустим, у вас одновременно выполняется не одна, а сразу несколько копий следующего кода:
unless (-e $filename) { # НЕВЕРНО!
open(FH, "> $filename");
# .. .
}
Между проверкой существования файла и его открытием для записи возникает возможность перехвата. Что еще хуже, если файл заменится ссылкой на что-нибудь важное (например, на ваш личный конфигурационный файл), предыдущий фрагмент сотрет этот файл. Правильным решением является неразрушающее создание функцией sysopen (см. рецепт 7.1). Setuid-сценарий CGI работает с другими правами, нежели Web-сервер. Так он получает возможность работать с ресурсами (файлами, скрытыми базами данных паролей и т. д.), которые иначе были бы для него недоступны. Это может быть удобно, но может быть и опасно. Из-за недостатков setuid-сценариев хакеры могут получить доступ не только к файлам, доступным для Web-сервера с его низкими привилегиями, но и к файлам, доступным для пользователя, с правами которого работает сценарий. Плохо написанный сценарий, работающий с правами системного администратора, позволит кому угодно изменить пароли, удалить файлы, прочитать данные кредитных карт и совершить иные злодеяния. По этой причине программа всегда должна работать с минимальным возможным уровнем привилегий, как правило - со стандартными для Web-сервера правами nobody. Наконец, принимайте во внимание физический путь вашего сетевого графика (возможно, это самая трудная из всех рекомендаций). Передаете ли вы незашифрованные пароли? Не перемещаются ли они по ненадежной сети? Поле формы PASSWORD защищает лишь от тех, кто подглядывает из-за плеча. При работе с паролями всегда используйте SSL. Если вы серьезно думаете о безопасности, беритесь за броузер и программу перехвата пакетов, чтобы узнать, легко ли расшифровать ваш сетевой трафик.
> Смотри также -----------------------------
Perlsec(l); спецификации CGI и HTTP, а также список FAQ, по безопасности CGI, упомянутые во введении этой главы; раздел "Avoiding Denial of Service Attacks" в стандартной документации по модулю CGI; рецепт 19.6.