Трусливый auth php register.

Ограничение доступа к какой-либо области сайта обычно выглядит
однообразно: каждому пользователю выдается логин и пароль или он сам
их выбирает, и для входа в защищенную часть сайта их нужно ввести. С технической же точки зрения для проверки пароля используются
разные методы. Для ввода логина и пароля может использоваться HTML-форма.
В этом случае пароль передается на сервер открытым текстом в POST-запросе.
Это неприемлемо, если пользователь сидит в локалке, где возможно
использование снифера. Для решения этой проблемы придуман метод
аутентификации с помощью хешей, при котором пароль не передается, а
передается хеш строка, зависящая от пароля, некоего одноразового
параметра и, возможно, еще от каких-либо параметров. Этот метод еще
называют challenge/response, поскольку при его использовании клиент
получает запрос с одноразовым параметром и посылает ответ, содержащий хеш. На уровне протокола HTTP 1.1 возможна аутентификация методом
Basic, что ни чем не лучше использования HTML-формы, и Digest, который
мы и рассмотрим подробно.

При использовании метода Digest, как уже было сказано, пароль
не передается, и его невозможно отснифить, однако есть и другая сторона
проблемы. Для того, чтобы проверить пароль, сервер должен вычислить
ответ и сравнить его с ответом клиента, следовательно, на сервере должен
храниться пароль или зависящие от него данные, необходимые для
прохождения аутентификации. Отсюда следует, что человек, получивший права
на чтение аккаунтов (например, с помощью SQL-injection), сможет получить
доступ к страницам, защищенным методом Digest. При использовании метода
Basic возможно хранение хешей вместо паролей, что не дает поднять права,
прочитав эти хеши (ниже мы увидим, что в Digest тоже могут храниться хеши,
но такие, что их знания достаточно для вычисления ответа). Таким образом, перед нами дилемма: либо наш пароль отснифят,
либо получат через web-уязвимость, которую кто-нибудь обязательно отыщет,
потому что кто ищет, тот всегда найдет. Есть метод аутентификации без
обоих этих недостатков - метод аутентификации на основе открытого ключа:
для проверки нужен открытый ключ, а для прохождения проверки - секретный,
однако в HTTP 1.1 такой метод не предусмотрен. RFC 2069
рекомендует использовать SSL, если защита так важна. Защищается только передача пароля, а контент не шифруется, так
что нет смысла защищать этим методом ресурсы, откуда пользователь
получает секретную информацию. Для них необходим SSL. А имеет смысл
защищать, например, форум или заливку контента на сайт. Итак, если хостинг не поддерживает SSL, а аутентификация должна
быть безопасной, то будем использовать Digest. В Apache предусмотрен модуль mod_digest. Для его использования
в конфиге (или в.htaccess) пишем:

AuthType Digest
AuthUserFile
AuthName
Require valid_user

Файлы пользователей создаются утилитой
htdigest. Про mod_digest одно время появлялись сообщения, что он уязвим, так что,
возможно, там еще какие-нибудь проблемы обнаружатся. Кроме того, когда
я попытался его использовать у себя дома, получил ошибку
500 Server Internal Error. Кроме того, если добавление аккаунтов должно происходить
автоматически, и их должно быть много, они должны
храниться не в конфиге Апача, а в MySQL. Решение -
использовать PHP. В PHP нет встроенной поддержки этого
метода, поэтому его придется реализовать. Для этого необходимо изучить
этот метод подробно. Сразу замечу, что приведенная в этой статье
реализация работает только на Apache, так как полный доступ к заголовкам
запроса (функция apache_request_headers) работает только в Apache, а на
других серверах может отсутствовать. Нам же просто необходимо прочитать
заголовок Authorization.

Описание метода

Полностью описание метода можно прочитать в RFC 2069, а если
вкратце, то метод работает так. Когда сервер получает запрос, относящийся к защищенной области,
он выдает ошибку 401 Authorization Required и заголовок с запросом
аутентификации такого вида:

WWW-Authenticate: Digest realm="secure area", nonce="123456123456"

realm - это название защищенной области, а nonce - одноразовое
значение. Есть еще необязательные параметры, которые мы обсуждать
не будем. Клиент повторяет запрос, добавив к нему заголовок такого вида:

Authorization: Digest realm="secure area", username="123", uri="/index.php", nonce="123456123456", response="1234567890abcdef1234567890abcdef"

Параметр uri должен совпадать с URI в запросе, а response - это
ответ, который вычисляется так:

response = H(H(A1) + ":" + nonce + ":" + H(A2))
H - хеш-функция, по умолчанию MD5
A1 = логин + ":" + realm + ":" + пароль
A2 = метод запроса + ":" + URI
метод запроса - это GET, POST и тд.

Как видим, A1 не зависит ни от запроса, ни от одноразового
значения, поэтому на сервере может храниться не пароль, а
H(A1). Именно так это реализовано в mod_digest в Apache.
Однако этих же данных достаточно и клиенту. Злоумышленник, получив
этот хеш, может вычислить ответ по приведенным выше формулам и
сформировать HTTP-запрос, например, с помощью программы
AccessDriver и ее инструмента HTTP
Debugger. Подробнее этот процесс будет показан ниже. Сервер должен проверить, является ли одноразовое значение
тем, которое было ранее выдано клиенту и не устарело ли оно.
Если ответ соответствует параметру nonce, но значение этого параметра
не актуально, выдается описанный выше ответ с кодом 401 с той лишь
разницей, что в заголовок WWW-Authenticate добавляется параметр
stale=true, указывающий, что в доступе отказано лишь по этой причине,
и следует повторить попытку, не запрашивая у пользователя новый пароль. Это, имхо, неудобно, поскольку если такая ситуация возникнет
при запросе POST или PUT с большим блоком данных, то клиенту придется
передать все данные дважды. Во избежание этого стандартом предусмотрен
заголовок Authentication-Info, в котором сервер может при ответе на
успешный запрос сообщить клиенту следующее одноразовое значение.
Синтаксис такой же, как у WWW-Authenticate, кроме того что nonce
заменяется на nextnonce. Однако, судя по результатам моих
экспериментов, Opera игнорирует этот заголовок. Другое решение: в соответствии с
RFC 2068 (HTTP/1.1), сервер может ответить раньше, чем завершится запрос,
чтобы клиент прервал ненужную передачу данных, но на Apache+PHP это
не реализуется, поскольку скрипт начинает выполняться только после того,
как Apache полностью получит и пропарсит запрос.

Хранение данных между запросами

В реализации метода challenge/response на PHP есть тонкий момент.
Одноразовый параметр формируется и выдается клиенту в одном ответе, а
проверяется уже в другом сеансе работы скрипта.
То есть его необходимо сохранить от одного вызова скрипта до другого, и для этого придется
использовать файлы или БД. В моем примере используются файлы с именами,
соответствующими одноразовым значениям, а в самих файлах записаны
IP-адреса клиентов, которым они выданы. В примере не реализован сбор
мусора: надо периодически удалять старые файлы.

Разбор кода

Этот скрипт проверяет только пароль, и работает независимо от
логина. В зависимости от успешности проверки выдаются простые ответы.