АЛЕКСЕЙ МИЧУРИН
В чем сильные и слабые стороны HTTP digest-авторизации
Среди всех средств, предоставляемых протоколом HTTP, digest-авторизация стоит особняком. Почему? Чем она отличается от вездесущей basic-авторизации? Каковы её сильные и слабые стороны? Насколько хорошо она поддержана различным ПО и когда уместно её использовать?
В протоколе HTTP предусмотрено два типа авторизации. Basic-модификация получила весьма широкое распространение. «Системный администратор» уже писал о нём (№5, 2005 г., 88-92 с.). Тогда были детально рассмотрены связанные с авторизацией переменные окружения (AUTH_TYPE, REMOTE_USER, REDIRECT_REQUEST_METHOD, REDIRECT_STATUS, REDIRECT_URL), особенности взаимодействия механизмов авторизации и переадресации (в том числе при обработке ошибок, возникающих в защищённых зонах). Эти детали одинаковы для basic- и digest-процедур идентификации пользователей, и в настоящей статье мы уже не будем останавливаться на этих вопросах, а полностью сосредоточимся на специфике digest-авторизации.
Напомним только, что основным недостатком basic-процедуры является передача пароля в открытом виде. Причём пароль передаётся вновь и вновь при любом обращении к любому документу, находящемуся в защищённой области. Это обстоятельство облегчает перехват пароля и делает протокол ещё более уязвимым.
Кроме того, basic-авторизация не даёт возможности «разлогиниться» – прервать сессию. Это естественное следствие того, что никакой сессии и нет – прерывать просто нечего.
Basic-процедура не позволяет контролировать количество неудачных попыток регистрации. То есть позволяет «взламывать» защиту методом грубого подбора пароля.
Но существует и второй тип защиты – digest-авторизация. Давайте разберёмся, как она работает и какие преимущества даёт.
Digest-авторизация: диалог клиента и сервера
В общих чертах процедура digest-авторизации немного сложнее basic. Диалоги клиента и сервера весьма похожи. Однако при digest-авторизации пароль никогда не передаётся по сети в открытом виде.
При первом обращении к защищённой области клиент, естественно, не передаёт никакой идентификационной информации – он пока не знает, что её необходимо передать.
Вот заголовки обычного HTTP-запроса:
GET /a-docs/ HTTP/1.1
Host: 127.0.0.1:9090
User-Agent: Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.7.7) Gecko/20050420 Firefox/1.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ru,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: KOI8-R,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
|
Сервер рассматривает этот запрос и обнаруживает, что требуемый документ защищён, но запрос не содержит данных для авторизации. Тогда клиенту выдаётся сообщение об ошибке 401.
На самом деле в протоколе HTTP не предусмотрено две разные ситуации: «требуется авторизация» и «пароль неверен». Поэтому ответ с кодом 401 не означает «вы ввели неверный пароль» или «введите пароль»; один код используется в обоих случаях.
Вот, как выглядят заголовки ответа сервера:
HTTP/1.1 401 Authorization Required
Date: Tue, 18 Oct 2005 13:04:36 GMT
Server: Apache/1.3.33 (Unix)
WWW-Authenticate: Digest realm="Realm-A", nonce="3f19e93c8baf99b0fdb9f60d6a228bc61129640676"
Keep-Alive: timeout=15, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=iso-8859-1
|
Как и в случае basic-авторизации, сервер добавил заголовок WWW-Authenticate, в котором сообщил клиенту тип авторизации (Digest), но на этот раз здесь присутствуют два дополнительных параметра: realm и nonce. Realm передаётся и при basic-авторизации, но в digest-процедуре он играет более важную роль, к рассмотрению которой мы ещё вернёмся.
Получив такой ответ, клиент (браузер) запрашивает у пользователя имя и пароль для авторизации (рис. 1).
Рисунок 1. Типичный диалог авторизации
Так же, как и в случае basic-авторизации, в диалоговом окне отображается значение параметра realm.
Если пользователь откажется от ввода (нажмёт кнопку «Cancel»), то браузер покажет ему страницу, которая пришла с кодом 401 (как и при Basic-авторизации, эта страница загружается не в момент нажатия кнопки «Cancel»). Если же пользователь ввёл имя и пароль и нажал «OK», то на сервер отправляется запрос, снабжённый идентификационной информацией:
GET /a-docs/ HTTP/1.1
Host: 127.0.0.1:9090
User-Agent: Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.7.7) Gecko/20050420 Firefox/1.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ru,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: KOI8-R,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Authorization: Digest username="a", realm="Realm-A", nonce="3f19e93c8baf99b0fdb9f60d6a228bc61129640676",
uri="/a-docs/", response="60046f788896b2f05dee72e0e9240c4d"
|
Как видите, теперь в заголовке Authorization передаётся гораздо больше информации, чем при Basic-авторизации. Кроме типа авторизации, мы видим здесь пять параметров. Следует заметить, что это один из самых простых случаев. Вообще digest-взаимодействие допускает добрый десяток уточняющих параметров, призванных усилить защиту. Некоторые из них мы ещё обсудим, а пока давайте рассмотрим имеющиеся (все они являются обязательными).
- Параметр username сообщает серверу имя, которое ввёл пользователь.
- Realm – имя засекреченной области. Digest-механизм использует эту информацию для идентификации защищённой зоны. Поэтому этот параметр должен обязательно присутствовать в заголовке Authorization и совпадать со значением realm, переданным сервером в заголовке WWW-Authenticate. Концепцию зон мы рассмотрим чуть ниже.
- Параметр nonce содержит ту же строку, которую передал сервер в одноимённом параметре в ответе с кодом 401. Эта информация используется при вычислении параметра response (см. ниже).
- Uri – абсолютный путь к документу.
- Response – контрольная сумма. Это результат вычисления хэш-функции от комбинации вышеперечисленных параметров и пароля. Обычно вычисляется функция MD5 от строки, состоящей из перечисленных параметров, разделённых двоеточием. Но детали процедуры хэширования зависят от множества обстоятельств, некоторые из которых мы затронем ниже.
Получив идентификационную информацию в заголовке Authorization, сервер вычисляет хэш-функцию по тому же алгоритму, что и клиент, и сравнивает с response. Если совпадения не произошло, значит пароль или имя не верны, сервер снова выдаёт ошибку 401, и попытка авторизации повторяется. Если пользователь успешно идентифицирован, то клиенту выдаются обычный заголовок и требуемый документ:
HTTP/1.1 200 OK
Date: Tue, 18 Oct 2005 13:04:42 GMT
Server: Apache/1.3.33 (Unix)
Keep-Alive: timeout=15, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=koi8-r
|
Здесь может присутствовать дополнительный заголовок Authentication-Info, но мы не будем его рассматривать. Пока Apache не имеет никаких возможностей настройки, позволяющих усилить защиту, используя возможности этого заголовка. Модуль digest_module не использует этот заголовок, а модуль mod_auth_digest эксплуатирует его «фиксированным» образом, не допуская никакой ручной настройки.
К рассмотрению модулей Apache мы перейдём в следующей части этой статьи. А здесь осталось сказать, что далее клиент (браузер) передаёт заголовок Authorization автоматически при каждом обращении к защищённой области. Это очень похоже на basic-авторизацию, но теперь пароль не передаётся в открытом виде, и восстановить его, перехватив заголовок Authorization, практически невозможно.
Внимательный читатель наверное уже заметил уязвимость протокола, связанную с тем, что злоумышленник может перехватить заголовок, а потом просто использовать легальные значения username, realm, nonce, uri и response. Это откроет ему доступ к ресурсу и без знания пароля. На самом деле мы рассмотрели самый простой сценарий digest-авторизации и только основные параметры. Ниже мы увидим, средства, значительно снижающие возможности того, кто перехватил ваш HTTP-заголовок.
Настройка сервера
Здесь речь пойдёт об HTTP-сервере Apache. Мы постараемся ориентироваться на широкий круг читателей, многие из которых, возможно, не располагают собственным сервером (пользуются виртуальным хостингом) и не имеют возможности самостоятельно «пересобрать» серверное ПО из исходных кодов. Но совсем не затронуть этот вопрос нельзя. С него-то мы и начнём.
Сборка сервера
В Apache 1.1 впервые появился модуль digest_module. Он был предельно прост и реализовывал минимум digest-возможностей. (Именно этим модулем были сгенерированы заголовки, которые мы видели выше как пример самого простого digest-диалога.) В версии 1.3.8 был добавлен модуль mod_auth_digest со статусом «экспериментальный». В нём были продублированы функции digest_module и добавлено несколько новых возможностей. Естественно, использовать эти модули совместно нельзя, так как наборы их функций частично перекрываются. В Apache 2.0 модуля digest_module уже нет. В документации на странице, посвящённой переходу на новую версию, сказано, что теперь mod_auth_digest не является экспериментальным. Однако на странице, описывающей сам модуль, чёрным по белому написано «Status: Experimental». Во всяком случае ни в одной из известных мне версий Apache digest-авторизация не включается при сборке с «умолчательными» параметрами.
Поэтому, если вы собираете сервер самостоятельно, дайте утилите ./configure соответствующий ключ. Они различны для разных версий Apache. Например, чтобы статически вкомпиллировать модуль mod_auth_digest в Apache 2.0, необходимо дать ключ --enable-auth-digest. Подробную справку по системе сборки имеющейся у вас версии сервера можно получить, запустив ./configure с ключом --help. Как всегда, не помешает почитать INSTALL, хотя в Apache 2.0 (после перехода на autoconf) он стал весьма лаконичным.
Если вы пользуетесь уже собранным сервером, то, скорее всего, вам не придётся предпринимать никаких специальных действий. Все известные мне сборки сервера 1.3 включают модуль mod_digest, которого вам хватит (на первое время). Сборки Apache 2.0 включают mod_auth_digest. Максимум, что может потребоваться от вас – это «раскомментировать» в файле httpd.conf инструкции, подключающие соответствующий модуль. Выглядеть это будет приблизительно так:
LoadModule digest_module libexec/apache/mod_digest.so
AddModule mod_digest.c
После того как Apache научился digest-языку, можно приступить к настройке.
Настройка защищённых зон
Для конфигурирования можно использовать файл глобальных настроек сервера, группируя директивы в блоки <Directory>, <DirectoryMatch>, <Files>, <FilesMatch>, <Location> или <LocationMatch> и защищая указанные файлы и директории. Можно использовать и локальные конфигурационные файлы .htaccess. В настоящей статье будем для определённости придерживаться второго пути. Но все приводимые конфигурационные директивы можно использовать и в любых других контекстах.
Следует также заметить, что все обсуждаемые в статье директивы принадлежат к группе AuthConfig. То есть, чтобы использовать их в .htaccess, у вас должны быть соответствующие права.
Самый простой пример
Давайте рассмотрим самый простой случай. Пусть нам необходимо защитить документы, находящиеся в некоторой директории (и всех вложенных в неё поддиректориях).
Сначала создадим файл с паролями. Для этого есть специальная утилита htdigest. В целом она похожа на htpasswd, но требует больше параметров:
htdigest [ -c ] passwdfile realm username
Ключ -c говорит о том, что файл с паролями надо создать. Если этот ключ отсутствует, то файл будет дополнен.
- passwdfile – имя файла с паролями.
- realm – имя защищённой зоны.
- username – имя пользователя.
Пароль по понятным причинам не указывается в командной строке, htdigest «попросит» ввести и подтвердить его.
То есть, чтобы создать файл passwd и пользователя a для зоны Realm-A, нам следует ввести следующую команду:
htdigest -c passwd Realm-A a
При добавлении новых пользователей ключ -c указывать не надо.
Чтобы снизить риск хищения секретной информации, файл с паролями лучше разместить где-нибудь вне дерева каталогов, доступных по HTTP.
Вернёмся теперь к директории, которую мы хотим защитить. Если в ней ещё нет файла .htaccess, то его следует создать.
Добавим в него следующие директивы:
AuthType Digest
AuthName Realm-A
AuthDigestFile /home/www/test/passwd
Require valid-user
Первая из них объявляет тип авторизации.
Вторая – имя защищённой зоны. Оно должно совпадать с именем, которое мы указали при создании файла passwd (регистр символов важен). Это имя будет отображаться в диалоге браузера с пользователем. Подробнее о зонах мы поговорим ниже.
Третья директива определяет местоположение файла с паролями.
Последняя конфигурационная инструкция в данном случае указывает на то, что доступ к директории разрешён только авторизованным пользователям. Она может задавать и более сложные условия. К этому вопросу мы тоже вернёмся.
Подробнее о зонах
В отличие от basic-авторизации аппарат digest-авторизации различает закрытые зоны. Каждый пользователь имеет право авторизоваться только в одной зоне. Этим зоны отличаются от групп. Если одному пользователю необходимо иметь доступ к нескольким зонам, то для него надо завести соответствующее число учётных записей. Причём в разных зонах один и тот же пользователь может иметь разные пароли.C точки зрения системы безопасности это разные пользователи с одинаковыми именами. Но даже если в разных зонах имеются учётные записи с одинаковым именем пользователя и одинаковым паролем, то этому пользователю придётся вновь авторизоваться при входе в новую зону, даже если ранее он уже прошёл процедуру авторизации (с теми же именем и паролем) в другой зоне.
Несмотря на то, что концепция зон вносит некоторое усложнение, она часто оказывается очень полезной. Дело в том, что файлы, подлежащие защите, не всегда можно расположить в одной директории и её поддиректориях. В случае такого дробления закрытых областей можно объявить соответствующие директории одной зоной, и повторной авторизации при переходе из директории в директорию уже не понадобится.
Чтобы лучше понять, как пользоваться зонами, давайте рассмотрим дерево директорий, показанное на рис. 2.
Рисунок 2. Дерево директорий с двумя защищёнными зонами. Незащищённые директории отмечены зелёным, принадлежащие зоне Realm-A – жёлтым, Realm-B – красным
Как видите, мы создали восемь директорий. Шесть из них защищены, на схеме они снабжены шапками, в которых указано, к каким зонам они принадлежат. Чёрные шапки говорят о том, что в данной директории есть .htaccess, белые – о том, что зона унаследована от родительской директории. Незащищённые элементы показаны зелёным цветом, зона Realm-A – жёлтым, Realm-B – красным.
При такой организации защиты пользователю достаточно один раз авторизоваться в зоне Realm-B, «войдя» в директорию /b-docs, и он может обращаться к скриптам из директории /cgi-bin/b-bin, не проходя процедуру идентификации повторно.
После авторизации в /b-docs при первом обращении к /cgi-bin/b-bin браузер, который пока не «знает», что обращается к закрытой зоне, отправит обычный запрос. Дальше всё будет происходить по уже известному сценарию. Сервер выдаст ответ об ошибке 401 с сообщением о том, что данная директория принадлежит к зоне Realm-B. Тогда браузер автоматически сформирует новый запрос с заголовком Authorization. Для пользователя весь этот диалог останется не заметен, вводить имя и пароль повторно не потребуется – браузер уже располагает всей идентификационной информацией для работы в зоне Realm-B.
Самая экзотическая ситуация создана в директории a-docs. В ней находится файл .htaccess, приписывающий её к закрытой зоне Realm-A. Если бы этим дело ограничивалось, то и её поддиректории – sub-a и sub-b – принадлежали бы к этой зоне. Так и происходит с директорией /adocs/sub-a: она не содержит собственного .htaccess и просто наследует все свойства директории a-docs. Однако в директории /a-docs/sub-b имеется собственный .htaccess, согласно которому она принадлежит к зоне Realm-B. По каким правилам взаимодействуют зоны в подобном случае, когда они перекрываются? Безусловно, доминировать будет зона Realm-B. То есть, чтобы получить доступ к директории/adocs/sub-b, не надо быть авторизованным в зоне RealmA, требуется только право доступа к зоне Realm-B.
В заключение заметим, что и basic-авторизация тоже допускает использование зон, но в этом случае клиент не сообщает серверу имя зоны и привязка пользователя к зоне не производится. Фактически информацию о зоне может использовать только клиент.
Группы пользователей
Как и basic-авторизация, аппарат digest-авторизации позволяет организовывать пользователей в группы. Для этого существует директива:
htdigest -c passwd Realm-A a
Она задаёт путь к файлу, описывающему группы. Его структура очень проста, и никаких специальных инструментов для его создания не существует. Это просто текстовый файл, каждая строка которого описывает одну группу и имеет следующую структуру:
имя_группы: пользователь ещё_пользователь ...
После того как файл с группами создан и путь к нему указан, можно использовать директиву Require следующим образом:
Require group имя_группы ещё_имя_группы ...
Доступ будет разрешён только членам перечисленных групп.
Однако следует помнить, что просмотр текстового файла с описанием групп – достаточно непроизводительная операция. Возможно, лучшим решением будет прямое указание списка пользователей, имеющих доступ к ресурсу, непосредственно в директиве Require. Делается это аналогично:
Require user имя_пользователя ещё и_ещё ...
Тогда авторизоваться смогут только перечисленные пользователи.
Информация о принадлежности пользователя к той или иной группе используется только директивой Require и недоступна ни клиенту, ни даже приложениям, работающим на сервере.
Взаимодействие авторизации и других механизмов ограничения доступа
Вы видите, что digest-аппарат предлагает очень гибкую систему настройки с зонами и группами пользователей. Но механизм авторизации – не единственное средство ограничения доступа. Давайте, не уходя далеко от темы статьи, рассмотрим, как механизм авторизации взаимодействует с другими средствами.
Для примера в качестве конкурента авторизации рассмотрим ограничение, позволяющее обращаться к ресурсу только с определённого хоста. Это ограничение реализуется следующими конфигурационными директивами:
Order Deny,Allow
Deny from all
Allow from 192.168.1.104
Что будет, если в одном .htaccess разместить и эти три строки, и четыре директивы, требующие авторизации? Чтобы разрешить этот вопрос, существует инструкция Satisfy, допускающая два значения единственного аргумента – Any или All.
Если в этот же файл .htaccess добавить:
Satisfy All
то для доступа к документам потребуется выполнение обоих условий: клиент должен обратиться к серверу с указанного хоста и предъявить надлежащую идентификационную информацию.
Если заменить All на Any, то для доступа потребуется выполнение хотя бы одного из требований. То есть если пользователь «пришёл» с указанного адреса, то авторизация ему не потребуется, а если он пришёл с любого другого хоста, то ему достаточно просто авторизоваться.
Любой из этих вариантов может оказаться полезен, в зависимости от конкретной задачи.
Иллюзии и реальность: digest vs basic
Теперь, когда мы познакомились с устройством и возможностями digest-аппарата, можно обсудить его преимущества и недостатки. Подытожим сначала, какие проблемы basic-авторизации так и остались не решены.
Во-первых, при digest-авторизации не создаётся сессии. На первый взгляд идентификатором сессии может показаться параметр nonce, передаваемый при каждом обращении к серверу в заголовке Authorization, но это впечатление обманчиво. Nonce позволяет не передавать пароль в открытом виде, и только. Сервер не запоминает все выданные nonce и не следит за ними.
Прямым следствием этого становится невозможность разорвать сессию – «разлогиниться».
Этот недостаток роднит digest- и basic- механизмы. Он, очевидно, связан с самой природой протокола HTTP, для которого не существует понятия «сессия».
Теперь понятно, что digest-авторизация, как и basic-авторизация, не в состоянии создать ощутимые препятствия для грубого перебора паролей. Справедливости ради следует отметить, что в полном описании digest-процедуры предусмотрены средства (дополнительные переменные и алгоритмы работы с ними), позволяющие детектировать подобную атаку и создать ощутимые препятствия для её осуществления. Но мне не известно ни одной программы, в которой эти средства были бы реализованы в полной мере. (Обсуждению реализаций посвящена следующая часть статьи.)
Вторым важным недостатком digest-защиты является то, что при усиленной охране пароля информация остаётся не зашифрованной и передаётся в открытом виде. Злоумышленник не может перехватить пароль, но он может перехватить информацию. Вы сохраните ключи от сейфа, но потеряете его содержимое.
Корни этой проблемы тоже кроются в протоколе HTTP, и, конечно, проблема пропадёт автоматически, если воспользоваться протоколом SHTTP, защищающим все передаваемые данные. Но это уже, согласитесь, несколько другой протокол.
Но (и это в-третьих) самый интересный вопрос: а защищён ли на самом деле пароль? Ответ на него не так прост.
Казалось бы, что может быть надёжнее MD5-хэширования? Но давайте приглядимся. Злоумышленник может перехватить заголовок – это факт. Нужен ли тогда ему пароль? Очевидно – нет! Достаточно включить в запросы перехваченный заголовок Authorization и – о чудо! – вы получаете доступ к секретной информации и не зная пароля. Конечно, Internet Explorer не поможет произвести подобный взлом, но тот, кто смог перехватить ваш трафик, сумеет найти подходящий инструмент.
Безусловно, большой ложкой дёгтя для злоумышленника будет то, что при хешировании пароля используется и URI документа. То есть для разных документов переменная response должна иметь разные значения, и вычислить их можно, только зная пароль. Так что один заголовок может открыть доступ только к одному документу. (Напомню, что при basic-авторизации один перехваченный заголовок открывает полный доступ ко всей закрытой области.) Но один документ – тоже очень немало, если это лента новостей или нечто в этом роде. Кроме того, можно собрать коллекцию заголовков – по одному на каждый документ.
Так что же получается, что защита пароля на самом деле – фикция? Не совсем. Модуль mod_auth_digest предоставляет множество новых конфигурационных директив (мы ещё вернёмся к некоторым из них), среди которых есть одна, способная помочь в решении обозначенной проблемы.
Речь идёт о директиве AuthDigestNonceLifetime. Её единственный аргумент задаёт время, в течение которого параметр nonce остаётся актуальным. По истечении этого времени информация, передаваемая в заголовке Authorization, «устаревает», и клиенту приходится авторизоваться снова. Это происходит автоматически по обычному сценарию. Просто на очередной запрос браузер получает ответ 401, в котором указан новый параметр nonce, но уже известная браузеру зона. Поэтому браузер вычисляет новый параметр response (по новому nonce и уже известному паролю) и формирует новый запрос с «правильным» заголовком Authorization, не причиняя пользователю никаких неудобств.
Дополнительное усиление защиты создаётся тем, что сервер и клиент ведут счёт соединениям и используют этот счётчик при вычислении хэш-суммы response. То есть значение параметра response при каждом новом соединении оказывается новым. За более подробной информацией вы можете обратиться к третьей главе RFC-2617, параметры nc, state и другие; здесь же мы не будем чрезмерно вдаваться в детали. Главный вывод таков: использование счётчиков делает перехват заголовков практически бесполезным; но используются счётчики только более современным модулем mod_auth_digest, модуль digest_module не способен работать с nc и сопутствующими параметрами.
Этот комплекс мер значительно снижает целесообразность перехвата Authorization-заголовков. Хотя указывать слишком малые времена актуальности не стоит. Так вы можете создать практически непреодолимые трудности для пользователей, работающих по медленным каналам.
Готовый рецепт взлома пароля
В рамках одной статьи трудно осветить все степени защиты, которые обеспечивает digest-авторизация (к сожалению, далеко не все из них реализованы на практике). Так мы даже не коснулись ещё одного HTTP-заголовка Authentical-Info, который не является обязательным (и используется редко), но позволяет защититься от многих типов атак. Мы не рассмотрели конфигурационную директиву AuthDigestRealmSeed, позволяющую немного усилить защиту при использовании mod_digest, но практически бесполезную для mod_auth_digest, который предоставляет гораздо более мощные средства. Эти и многие другие детали и тонкости, устаревшие или не имеющие принципиального значения, мы оставим специалистам. А здесь отметим, что если вы базируетесь только на материале этой статьи, то не факт, что атака, придуманная вами, будет успешной.
Однако чтобы охладить преждевременное ликование оптимистов, полагающих, что digest-схема надёжно защищает пароль, я приведу ещё один пример организации атаки.
Она базируется на перехвате и подмене трафика. Опытному программисту, имеющему под рукой исходный код Apache, потребуется несколько часов, чтобы написать специальный прокси-сервер, который будет делать следующее: получив от сервера ответ 401 с требованием авторизоваться по digest-схеме, прокси отправляет клиенту изменённый ответ 401, требующий basic-авторизации; клиент авторизуется по basic-схеме и отправляет хозяину прокси-сервера имя и пароль в открытом виде (как того требует basic-процедура); всё – злоумышленник получил, что хотел. В принципе, зная имя и пароль, прокси-сервер может создать легальный digest-запрос для сервера и далее поддерживать общение клиента и сервера, оставаясь полностью прозрачным.
Возможность такой атаки, казалось бы, сводит на нет все преимущества digest-схемы, но на самом деле подобным атакам в той или иной мере подвержены все системы защиты. Злоумышленник может подменить программу login, может прослушивать клавиатуру и прочее. Этот сценарий атаки (MITM – man in the middle) – один из самых древних и универсальных. Он с успехом применялся задолго до появления вычислительной техники: от радиоперехвата до подделки берестяных грамот («и в суму его пустую суют грамоту другую» А.С. Пушкин). Совсем неудивительно, что этот тип атаки применим и в нашем случае.
Тем, кто хочет узнать ещё десяток способов «сломать» digest-защиту, я бы посоветовал ознакомиться с четвёртой главой RFC-2617.
Стандарты и реализации
Детали digest-авторизации исчерпывающе описаны в RFC-2069. Этот документ датирован 1997 годом. Уточнения были сделаны в RFC-2617 в 1999 году. Но, невзирая на то, что с тех пор сменилось тысячелетие, ни браузеры, ни сервера не выполняют всех требований и рекомендаций, изложенных в RFC.
В RFC описано множество возможностей digest-авторизации, управляемых добрым десятком параметров. Но Apache до сих пор не позволяет никаким образом влиять на большинство из них.
В модуле mod_auth_digest появилось множество настроечных директив, но реальных возможностей настройки прибавилось гораздо меньше. Так, например, AuthDigestAlgorithm позволяет выбрать хэш-функцию. Допустимы два значения параметра: MD5 и MD5-sess, но использовать можно только MD5. Появилась директива AuthDigestNcCheck, которая в будущем позволит работать с параметром nc (этот параметр содержит счётчик соединений, о котором упоминалось выше), но сейчас она ничего не делает. Одними словом, большинство новых директив просто зарезервированы.
Таким образом, относительно сервера Apache можно констатировать два факта: первый – реализованы далеко не все возможности, заявленные в RFC, и второй – работы ведутся весьма активно, причём план работ (RFC) имеется, что особенно обнадёживает.
Большинство современных браузеров поддерживают digest-авторизацию. В их числе Internet Explorer (о нём требуется отдельный разговор), Fire Fox, Opera, Mozilla, Netscape, Amaya. Лично я сталкивался с отсутствием digest-авторизации, только работая браузером lynx (версия 2.8.5) и... Internet Explorer.
Microsoft went one way; everyone else went the other way
В подзаголовок вынесена цитата из статьи в eWeek (http://www.eweek.com/article2/0,1759,1500432,00.asp), в которой с должной долей драматизма описан очередной конфликт софтверного гиганта со всем миром; на этот раз – на почве разного понимания RFC-2617. Вследствие этого IE – единственный браузер, не способный работать с Apache, когда дело касается digest-авторизации. Более того, IE – единственный браузер, способный работать с IIS по «digest»-схеме, как её понимает Microsoft. Причём MS не считает это ошибкой и не собирается ничего менять.
Информации по этой проблеме очень мало, и она очень противоречива. Чтобы понять, почему так происходит, давайте рассмотрим поведение IE 6.0 (с другими версиями дела обстоят или так же или хуже).
Исторически, во времена версий 1.1 и 1.2, разработчики Apache заняли очень твёрдую позицию и не стали поддерживать Microsoft-интерпретацию RFC. Поэтому, если сервер оснащён старым модулем digest_module, пользователи IE 6.0 вообще не смогут на нём авторизоваться.
Но Microsoft проявил не меньшую принципиальность и не стал ничего менять в своей реализации HTTP/1.1. Поэтому в новом модуле mod_auth_digest протокол был «усовершенствован». Теперь IE мог авторизоваться и даже работать по Digest-сценарию за одним исключением: сервер не мог корректно обработать запрос GET, если строка запроса была не пустой. Это связано с тем, что IE нарушает и другие стандарты, не связанные с digest-авторизацией. Microsoft имеет собственную точку зрения на то, что такое URI и другие фундаментальные понятия.
Некоторое время (от версии 1.3.8 до 2.0.51) разработчики Apache ожидали изменений в IE, но шаг навстречу пришлось делать снова им. В Apache 2.0.51 модуль mod_auth_digest стал обрабатывать переменную окружения AuthDigestEnableQueryStringHack. Если она установлена, то модуль «на лету» корректирует HTTP-заголовки, придавая им стандартный вид. То есть, чтобы общение с IE шло без сбоев, следует использовать директиву:
BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On
Тогда сервер будет распознавать самый великий браузер и на лету подставлять ему костыли.
Мои личные тесты, проведённые на apache-2.0.55, показали, что если браузер «притворяется» Explorer (как это делает Opera), но соблюдает все нормы, то никакого конфликта не возникает. Видимо, это сделано в расчёте на то, что IE когда-нибудь начнёт работать по общепринятым правилам. То есть, согласно результатам этих тестов, можно установить переменную AuthDigestEnableQueryStringHack безусловно:
SetEnv AuthDigestEnableQueryStringHack On
Но документация на Apache рекомендует первый способ, не исключено, что на это есть какие-то причины.
Неразбериха же возникает от того, что большинство обзоров пытаются ответить на вопрос: «имеет ли IE версии X возможность Y?» Но теперь вы видите, что в данном случае результат тестирования определяется не версией клиента, а мельчайшими деталями настройки сервера. Поэтому и возникает тот разброс мнений, который легко наблюдать в Internet. Дело усугубляется ещё и тем, что, с точки зрения любого другого браузера, все три перечисленные конфигурации сервера одинаково работоспособны.
На мой взгляд, всё это ещё один пример того, как Microsoft открыто вытирает ноги о весь мир.
К сожалению, у меня не было возможности протестировать сервер IIS на предмет его совместимости с какими-либо браузерами, отличными от IE. Но лично у меня нет оснований не доверять eWeek.
Преимущества digest-авторизации
Итак, мы рассмотрели тонкости digest-схемы авторизации и обнаружили массу недостатков. Но надо ли после этого ставить на ней крест? Очевидно, нет. Наивно было бы ожидать, что какая-либо надстройка над HTTP сможет изменить его природу и сделает его надёжнее специализированных средств, таких как SHTTP и TLS. Но несмотря на то, что digest-процедура не исключает возможности многих видов атак, она всё же оказывается более защищённой, чем basic. Digest-схема имеет некоторые преимущества и перед SSL: она проще в настройке, работа по алгоритмам digest-авторизации не создаёт практически никакой вычислительной нагрузки на систему, кроме того, не будем забывать, что SSL-сертификаты не бесплатны. Наконец, digest-схема превосходит по стойкости многие протоколы, распространённые не менее, чем протокол HTTP. Не секрет, что пароль передаётся в открытом виде в POP3, IMAP, FTP и многих других протоколах.
Конечно, пока трудно найти клиента или сервер, где были бы реализованы все возможности digest-авторизации, предусмотренные в RFC. (По крайней мере, лично мне не известна ни одна такая программа.) Но работа ведётся. Нововведения Apache 2.0 (директива AuthDigestNonceLifetime) уже делают digest-авторизацию весьма ценным – простым и эффективным – инструментом, при обеспечении безопасности быстро меняющейся информации.
Дополнительным преимуществом digest-аппарата является гибкая работа с секретными зонами – ещё одним средством (наряду с группами) для регламентирования полномочий пользователей.
Таким образом, невзирая на все имеющиеся недостатки, уже сегодня digest-авторизация поднимает HTTP-протокол над многими не менее распространёнными в Сети протоколами. Она не делает HTTP самым защищённым, но и не лишает его простоты, лёгкости и открытости.