Каждый веб-мастер рано или поздно начинает задумываться о безопасности веб-приложений (сайтов в целом, cgi-програм), которые ему приходиться писать. Поэтому целесообразно будет рассмотреть cgi-интерфейс, как один из возможных источников опасности. Поэтому возникает проблема скрытия обработчика cgi-запросов, а также передаваемой информации. Однако всегда следует помнить о том, что защита работает только для тех, кто не может её обойти. Итак начнём с основ.
Введение в CGI
CGI - Common Gateway Interface - виртуальный шлюзовой интерфейс, являющийся стандартом, который предназначен для создания и функционирования HTTP приложений. Приложение HTTP (CGI-программа) запускается сервером в реальном времени на стороне сервера (схема работы "клиент-сервер"). Сервер передаёт запросы, поступающие от пользователя, CGI-программе, которая их обрабатывает и возвращает результат своей работы на экран пользователя. Таким образом, посетитель получает динамическую информацию, которая может изменяться в результате влияния различных факторов. Сам шлюз ( скрипт CGI ) может быть написан на различных языках программирования: PHP, C/C++, Fortran, Perl, TCL, Unix Schell, Visual Basic, Apple Script и других подобных языках.
Передача данных
Для передачи данных об информационном запросе от сервера к шлюзу, сервер использует командную строку и переменные окружения. Эти переменные окружения устанавливаются в тот момент, когда сервер выполняет программу-шлюз (далее просто "шлюз").
Информацию шлюзам передаётся в следующей форме:
имя_переменной=значение&имя_переменной2=значение&...
где имя_переменной - это реальной имя переменной, определённое, скажем в дескрипторе FORM, значение - это реальное значение той или иной переменной, которое также может быть получено из дескриптора FORM.
В зависимости от метода, который используется для запроса (GET или POST), эта строка поставляется или как часть URL (если это GET запрос), или как содержимое HTTP запроса (если это POST запрос). При использовании POST запроса эта информация будет передана шлюзу через стандартный поток ввода.
Способы маскирования запросов
Все приводимые ниже методы маскирования CGI-запросов реальны и могут вполне рассматриваться как готовые решения по маскированию CGI-запросов. Каждый метод рассматривается со стороны: во-первых, скрытия CGI-шлюза, во-вторых, скрытие возможной информации, если таковая передаётся.
МЕТОД №1: "Проще простого"
Передача данных из формы (дескриптор FORM) методом POST (т.е. вне адресной строки) уберегает от визуального контакта, который может состояться между возможным нарушителем и передаваемыми данными. Т.к. помимо очевидных для возможного нарушителя данных, которые он ввёл в рамках дескриптора FORM, могут передаваться ещё и скрытые данные, которые до этого ему были не видны (дескриптор input с атрибутом type=hidden). Скрытие передаваемых данных обеспечивается благодаря самой возможности CGI-интерфейса, а именно наличие метода передачи данных POST. Однако это не спасает от скрытия cgi-программы, которая будет принимать и обрабатывать запросы, т.к её имя, а также её местоположение будет очевидно после приёма и обработки запроса.
МЕТОД №2: "Медовый пирог"
Применение фреймовой структуры html-документа вполне может решить проблемы маскирования cgi-запроса. В сущность фреймов уже заложена возможность скрытия адреса документа, загруженного в фрейм, а использования фреймовой структуры документа в совокупности с "клиентскими" языками программирования позволит полностью скрыть адрес и имя cgi-шлюза.
Во-первых, создадим сам документ. По желанию заказчика это может быть:
- "фрейм с осла" - фрейм занимает 100% доступной ширины и 100% доступной высоты окна браузера;
- "фреймовое меню" - более популярная версия - документ состоит минимум из двух фреймов, в одном размещается навигация, в другом открывается непосредственно контент-страница.
Остановимся на модели "фреймовое меню". Код такой страницы будет приблизительно таким (назовём её index.html):
Таким образом, мы делим документ по вертикали на два фрейма. В левый фрейм загружается документ nav.html, в правый фрейм загружается документ cont.html. Благодаря фреймам пользователю недоступна информация относительно адресов документов, которые загружаются во фреймы. Адресная строка будет отображать лишь адрес загруженного документа, а также название его название (index.html). Документы, загружаемы во фреймы, могут находиться либо на том же сервере, где и index.html, либо могут загружаться с других серверов (в этом случае у нужного фрейма в атрибут src нужно вставить абсолютный путь к документу, вместо относительного пути).
Во-вторых, организовываем навигацию. При организации ссылок внутри документов (при фреймовом разбиении основного окна) следует указывать, для какого фрейма предназначена та или иная ссылка, т.е. тот фрейм, в котором будет открываться документ. Стандартная ссылка выглядит таким образом:
Ссылка для фреймового документа с указанием фрейма, в который будет загружен открываемый документ:
Таким образом, при помощи атрибута 'target' дескриптора А определяется фрейм 'content' для загрузки в него документа cmd.cgi, который определяется атрибутом 'href' всё того же дескриптора А. Далее используем метод GET для передачи параметров программе cmd.cgi. Ссылка примет вид:
Теперь не составит труда организовать страницу nav.html, которая будет играть роль навигационного меню, а ссылки, которые будут содержаться на данной странице, будут управлять содержимым фрейма content, точнее ссылки будут отправлять запросы к программе cmd.cgi, а уж она будет возвращать требуемую информацию. Простейший вариант:
На данном этапе поставленная задача решается полностью. CGI-скрипт, который будет обрабатывать запросы, будет скрыт от пользователя, т.е пользователь всё время будет видеть документ index.html. Содержание фрейма content будет меняться программой cmd.cgi в зависимости от запросов пользователя, а именно перемещения по ссылкам навигационного меню. Передача данных из дескриптора FORM будет также защищена, вне зависимости от используемого метода передачи (GET или POST), при условии, что страница index.html не будет заменена другой страницей, по адресу которой будет переданы данные из дескриптора FORM.
Ложкой дёгтя в этом медовом пироге послужит сам инструмент пользователя, через который он будет осуществлять просмотр документа index.html. Большинство браузеров имеют строку состояния, в которой по умолчанию отображается статус загрузки текущего документа. Также данная строка состояния при наведении мышью на любую ссылку будет отображать содержания атрибута 'href', т.е. будет отображать URL того места, куда ссылается ссылка. В нашем случае во всех ссылках фигурирует cgi-скрипт cmd.cgi - именно то, что нужно скрыть.
Напрягаемся. Большинство браузеров должны поддерживать DOM - Document Object Model, так называемую объектную модель документа. Объектная модель документа DOM стандартизирована Консорциумом Всемирной Паутины - W3C (хотя современные браузеры данную спецификацию поддерживают либо не полностью, либо не точно, либо вообще не поддерживают).
Данное обстоятельство позволяет документу, загруженному в браузер, рассматривать сам браузер (с загруженным документом) как иерархически организованную совокупность объектов, свойства которых доступно для чтения, а большинство из них доступно и для записи.
Теперь разгребаем кучу объектов и их свойств, и находим единственный и полезный объект, который окажется для нас полезным в данной ситуации, который позволит решить сложившуюся проблему со строкой состояния. Дело в том, что строка состояния в объектной модели документа может рассматриваться как свойство 'status' объекта 'window'. Чудесен и тот факт, что данное свойство доступно для записи, а значит пользователя, в тот момент, когда он настырно водит по ссылкам и всматривается в строку состояния дабы узнать, куда ведут ссылки, можно обмануть, а именно подменить строку состояния, тем самым, скрыв реальное содержание атрибута 'href' дескриптора A.
Для осуществления задуманного подходит любой "клиентский" язык программирования, который бы понимал, как работать с объектной моделью. При использовании самого популярного "клиентского" языка JavaScript наше навигационное меню преобразится, и будет выглядеть так:
Добавляя в каждый дескриптор А обработчик событий onMouseOver, который средствами JavaScript будет менять строку состояния на поддельную при каждом наведении мышью на ссылку.
Таким образом, мы скрыли cgi-скрипт, который будет обрабатывать запросы. Задача выполнена. Однако не полностью.
Ещё одной ложкой дёгтя в нашем медовом пироге может стать сам пользователь, который в любой момент может тыкнуть правой кнопкой мыши в фрейм content, в открывшемся контекстном меню может выбрать 'Свойства', и в Свойствах странице он увидит URL страницы, а в нём и cgi-скрипт, и передаваемые переменные.
Опять на помощь приходит объектная модель документа. Всё что происходит в пределах окна браузера можно отследить, а значит можно и отследить щелчок правой кнопкой мыши. В данной проблеме нам поможет разобраться объект event и его свойство button. Которое в момент клика мышью будет содержать информацию о нажимаемой кнопке.
Для того, чтобы предотвратить безнаказанное кликанье правой кнопкой мыши во фрейме content, нужно чтобы любой загружаемый в него документ имел дескриптор BODY такого вида:
После такого добавления все попытки вызвать контекстное меню будут вызывать появления сообщения с кнопкой OK и надписью "НиЗЗя", спасая документ от просмотра его же свойств.
Итак, обе задачи выполнены полностью.
МЕТОД №3: "Рубим лишнее"
Совершенствуя метод "Медового пирога" рождается следующий метод: т.к. самым информативным источником информации о cgi-шлюзе и передаваемой информации служит сам браузер пользователя, то почему бы этот браузер не "обрезать", т.е. почему не убрать с глаз долой адресную строку и строку состояния. Конечно, это будет несколько не удобно для пользователя, но "красота требует жертв".
Для реализации данного метода воспользуемся языком JavaScript и объектной моделью документа. К сожалению, у основного окна браузера не удастся убрать ни адресную строку, ни строку состояния. Но почему это не сделать с новым окном, т.е. почему бы ни открыть новое окно, в котором все опасные части браузера будут отсутствовать, и предложить пользователю работать в такой "обрезанной" версии браузера, функциональность, то оно всё равно не потеряет.
Итак первоначально создаём главную страницу, которая будет вызывать "обрезанное окно".
Вызов метода open объекта window открывает новое окно, в которое загружается страница 'cmd.cgi?page=index'. Откуда грузится информация в окно пользователь увидеть не в силах, потому что ему просто негде это посмотреть. Однако всё ещё осталось возможность заглянуть в свойства документа по щелчку правой кнопки мыши. Эта проблема уже подлежала решению в методе "Медового пирога", так что просто добавляем соответствующий обработчик события в каждую загружаемую страницу.
Организация навигации, а именно ссылок навигации через cgi-шлюз организуется прямым вбиванием соответствующих обращений к шлюзу прямо в атрибут 'href' дескриптора А.
Содержания ссылок ни где не высветится, т.к. отсутствует строка состояния, а копирование текста ссылки не представляется возможным, т.к. контекстное меню, через которое это представлялось возможным, отключено средствами JavaScript.
Задача решена: cgi-шлюз скрыт, также обеспечивается скрытие всей передающейся информации.
МЕТОД №4: "Каждому лектору по вектору"
Использование сторонних продуктов, таких, например как продукт компании Macromedia под названием Macromedia Flash поможет решить поставленные задачи. Хотя создателю документа потребуются дополнительные знания данного продукта, а также его встроенного языка - ActionScript.
Сами средства векторной анимации, которые предоставляет нам Macromedia Flash, нам мало интересны. Интересны возможности языка ActionScript, а также интеграция его и самого конечного продукта с другими языками программирования и базами данных. Конечный продукт - это так называемый flash-ролик, которые представляет собой элемент active-x, который существует как самостоятельное приложение, и который, встраивается в html-документ по средствами включения его в тело документа как объекта active-x. Разумеется, для проигрывания такого ролика от браузера потребуется наличие установленного plug-in'а, но это не является проблемой, т.к. почти всё браузеры уже имеют предустановленный flash-plug-in, а если такового всё же в системе не имеется, то произойдёт автоматическая его загрузка с сервера производителя.
Чудесной особенностью технологии Flash является тот факт, что она вполне может заменить обыкновенную страницу, написанную на html. Но здесь открываются существенные преимущества технологии Flash перед языком HTML.
Организация ссылок внутри flash-ролика может происходить как обычно, т.е. пряма ссылка на какой либо документ/cgi-шлюз, либо ссылка может приводить к загрузке новых роликов, которые могут содержать данные, и которые будут являть аналогом документов, загружаемых по щелчку на обыкновенную html-ссылку. Ссылка на другие ролики происходит внутри ролика, текст ссылки не покидает пределов ролика, его нельзя скопировать, либо просмотреть в строке состояния. Также перемещение по таким ссылками не будет вызывать перезагрузку главной страницу, в которую вставлен flash-ролик (разумеется, если не указаны специальные инструкции в самом ролике). Также как и в фреймовом представлении документа (перезагружается содержание фреймов) , здесь перезагружается/догружается только сам ролик. Причём загружаемый ролик может находиться и на другом сервере - пользователь об этом никогда не узнает.
Технология FLASH средствами языка ActionScript позволяет обращаться непосредственно к любому cgi-шлюзу изнутри ролика, при этом скрывая от пользователя место обращения и сам запрос. Пожалуй, единственный недостаток данного метода - это ограниченность принимаемой от шлюза информации, т.е. flash-ролик не сумеет интерпретировать код html, части которого обычно встречаются в ответе на запрос к cgi-программе, зато без труда можно оперировать с простой текстовой информации. Конечно, функциональную ограниченность технологии FLASH можно расширить, написав на встроенном языке ActionScript дополнительные модули разбора и парсинга ответов cgi-программ (например, модуль интерпретации html внутри flash-ролика).
Итак, данный метод прекрасно подходит для маскирования cgi-запросов, но у программиста должны быть соответствующие навыки работы и программирования для технологии FLASH.
МЕТОД №5: "Бревно в глазу"
Почему бы ни сделать так, чтобы у пользователя вообще не возникало и мысли о том, что выдаваемый ему документ был сгенерирован динамически cgi-программой. Почему бы ни сделать так, чтобы запросы к cgi-шлюзу формировались не на стороне пользователя, а на стороне сервера. А если запросов на стороне клиента не будет, тогда и не будет потребности их от него скрывать, и уж тем более пропадёт потребность в скрытие cgi-шлюза. Но появится другая потребность: как бы сделать так, чтобы запросы формировались на стороне сервера.
Чтобы обмануть пользователя данным способ воспользуемся технологией SSI (Server Side Include). Само название данной технологии говорит нам о её предназначении - включение чего либо в что либо на стороне сервера, а именно включение содержимого файла, либо результата работы cgi-программы в любой другой документ, обычно это стандартный html-документ. Т.к. вся работа данной технологии происходит на сервере, то и пользователь не догадается о том, что содержание html-страницы было сгенерировано динамически, включено в этот самый документ ещё на стороне сервера, а уже потом только отправлено ему на просмотр. Анонимность cgi-шлюза обеспечивается системой безопасности веб-сервера.
Итак, главная страница представляет собой обыкновенный html-документ. Ссылки также вполне обыкновенные, ссылаются на другие не менее обыкновенные html-документы. За исключением одного пустяка - все страницы пусты внутри, а содержат только одну команду на включение динамического содержимого. Выглядит она приблизительно так:
Таким образом, вставляя данную директиву SSI в тело каждого html-документа между дескрипторами BODY (или же в том месте, где это необходимо), мы обеспечиваем осуществление cgi-запроса, который будет выполнен на стороне сервера.
При парсинге страницы, данная директива будет заменена на ответ на запрос 'cmd.cgi?page=first', после чего уже как страница html будет отправлена пользователю.
Минус данного метода лишь в том, что добавление дополнительных разделов приведёт к потребности создания дополнительных html-страниц с директивами внутри, которые будут содержать новые запросы. По той же причине, не будет возможности создавать запросы "на лету" (например, поисковый запрос). Хотя и это возможно при дополнительном конфигурировании веб-сервера, и написании на серверном языке программирования дополнительного парсингового модуля, который бы автоматически генерировал html-страницы с включёнными директивами, которые бы и содержали запросы, сгенерированные динамически.
МЕТОД №6 "Изобретенный велосипед"
А, собственно, зачем что-то изобретать? Ведь вполне возможно, что кто-то уже столкнулся с данной проблемой (ну может быть где-то похожей). Столкнулся и придумал грамотное решение. Именно таким человеком стал Ральф Энгельшел.
В апреле 1996 года им бал написан модуль для общеизвестного веб-сервера Apache под названием "mod_rewrite". В июле 1997 года этот модуль был эксклюзивно подарен компании "The Apache Group" - компании-разработчику веб-серверу Apache. С этих замечательных пор модуль mod_rewrite входит в поставку всех версий Apache.
После выхода в свет первой версии Apache, в которую был включён mod_rewrite, его (модуль mod_rewrite) сразу окрестили "швейцарским ножом URL преобразований".
Этот модуль использует механизм, основанный на правилах (синтаксический анализатор, основанный на регулярных выражениях) для преобразований URL на лету. Он поддерживает неограниченное количество правил и неограниченное количество связанных с правилом условий для реализации действительно гибкого и мощного механизма для URL преобразований. URL преобразования могут зависеть от разных критериев, например переменных сервера, переменных окружения, HTTP заголовков, времени и даже запросы к внешним базам данных в разных форматах, могут быть использованы для достижения действительно точного соответствия вашим ожиданиям, преобразованных URL.
Этот модуль оперирует с полными URL и в масштабе сервера (httpd.conf) и в масштабе каталога (.htaccess) и даже может генерировать части строки запроса в качестве результата. Преобразованный результат может приводить к внутренней обработке, внешнему перенаправлению запроса или даже к прохождению через внутренний прокси модуль.
Итак, попробуем решить нашу задачу при помощи модуля mod_rewrite. Перед началом работы, разумеется, данный модуль нужно включить. Для включения модуля в файл конфигурации веб-сервера Apache (httpd.conf) вставляем строки:
Модуль подключен. Теперь в файле локальной конфигурации (.htaccess) определим правила трансформирования URL. К примеру:
Первая строка - включение модуля (директива RewriteEngine). Затем идёт задание каталога, в котором будет работать правило (директива RewriteBase). И далее идёт определение самого правила (директива RewriteRule). Суть правила весьма проста: при запросе из корневого каталога файла rewrite.htm пользователю будет показано содержание файла rewrite.html, при этом для пользователя данное содержание будет как будто бы из файла rewrite.htm, а на самом деле оно будет взято из файла rewrite.html. Даже адресная строка будет отображать путь, который будет указывать на то, что в данный момент загружен файл rewrite.htm.
Для замены всех htm файлов html файлами правило будет выглядеть следующим образом:
Для решению более сложных задач преобразования, таких как например маскирования cgi-запроса, а также информации, передаваемой методом GET применяем регулярные выражения. К примеру, преобразование строки "cmd?page=first" в строку "/index37/" будет осуществляться правилом:
А преобразование строки "cmd.cgi?page=first&id=157" строку "page_first_157.html" будет осуществляться правилом:
Таким образом, можно добиться полного скрытия cgi-шлюза, а также передаваемых параметров. Пользователь никогда и не догадается о том, что html-страница, которую он просматривает, на самом деле генерируется динамически cgi-программой.
Плюсы данного метода на лицо: прекрасно решает поставленные задачи. Однако вся эта функциональность и гибкость имеет свой недостаток: сложность.
Вместо заключения
Потребность скрыть CGI-шлюз, который будет принимать запросы - скорее вынужденная мера, чем эффективная. Малоинтересно рассматривать данные способ защиты, как действительно реальные способы защиты. Подобные меры предосторожности применимы скорее для защиты от неподготовленных пользователей (можно также рассматривать как средство повышению удобства пользования), в то время как нарушителю с минимальными знаниями о функционировании CGI-интерфейса, знаниями так называемых "клиентских" языков программирования и разметки не составит труда определить реальный скрипт, который принимает и обрабатывает запросы. Хотя это и потребует дополнительных умственных и временных затрат.