Form-based authentication для веб-сайтов
Мы верим, что Stack Overflow должен быть не просто ресурсом для очень узких технических вопросов, но также содержать общие рекомендации о том, как решить множество общих проблем. "Form-based authentication для веб-сайтов" должно стать хорошей темой для подобного эксперимента.
Оно должно включать следующие темы:
Как авторизоваться
Как оставаться авторизованным
Как хранить пароль
Использование секретных вопросов
Как организовать восстановление имени пользователя/пароля
OpenID
Галочка "Запомнить меня"
Автозаполнение имени пользователя/пароля
Секретные URL (публичные URL, защищённые дайджест-аутентификацией)
Проверка силы пароля
и многое другое о form-based website authentication...
Оно не должно включать такое, как:
Роли и авторизация
Основы HTTP аутентификации
Пожалуйста, помогите нам
Предлагая подтемы
Присылая хорошие статьи по теме
Дополняя официальный ответ
Также вы можете ознакомиться с оригиналом вопроса на Stack Overflow
Ответ
Часть первая: как авторизоваться
Мы предполагаем, что вы уже знакомы с тем, как построить форму вида "логин+пароль", которая отправляет POST запрос на сторону сервера. Раздел ниже будет описывать паттерны аутентификации для практического применения и то, как избежать самые распространённые "подводные камни".
HTTPS или не HTTPS, вот в чём вопрос
Только если ваше соединение уже не защищено (что значит оно не туннелировано через HTTPS с использованием SSL/TLS), данные из вашей формы входа будут преданы в виде простого текста, что позволяет любому находящемуся на линии между вашим браузером и сервером подслушивать эти данные в момент передачи. Если вы передаёте любые нетривиальные данные, используйте HTTPS.
В сущности, единственный реальный способ защититься от перехвата / анализа пакетов во время авторизации — использование HTTPS или другой схемы, основанной на сертификации (например TLS), или на доказанных и протестированных схемах типа "вызов-ответ" (как протокол Диффи — Хеллмана). Любая другая схема может быть с лёгкостью обойдена атакующим.
Конечно, вы можете пойти другим путём и использовать какие-нибудь системы двухэтапной аутентификации (в т.ч. Google Authenticator, кодовую книгу или генератор RSA ключей). Если вы правильно их используете, то всё должно работать даже без защищенного соединения, но трудно представить себе разработчика, который станет использовать двухэтапную аутентификацию, но при этом не использует SSL.
(Не) используйте свои собственные криптографические JavaScript-решения
Взяв во внимание ненулевую стоимость и ощутимую сложность установки SSL сертификата на своём веб-сайте, некоторые разработчики предпринимают попытки создания собственных внутрибраузерных криптографических решений чтобы избежать передачи данных входа в явном виде по незащищённому каналу.
Несмотря на всю красоту идеи, по существу это бесполезно (и даже опасно для безопасности), если только данное решение не будет совмещено с вышеизложенными схемами, а именно: либо защита линии сильным шифрованием, либо использование проверенного временем механизма типа "вызов-ответ" (если вы не знаете, что это такое, просто знайте, что это одна из самых сложных с точки зрения задумки и воплощения в жизнь концепций цифровой безопасности).
Капча - враг человека
CAPTCHA призвана мешать исполнению одной конкретной категории атак: подбор по словарю (брутфорс) без участия человека. Вне всякого сомнения это настоящая угроза, однако есть более элегантные способы борьбы с ними и без применения капчи, но мы поговорим о них позднее.
Знайте, что реализации капчи различны; они часто нерешаемы человеком, многие из них неустойчивы против ботов, абсолютно все бесполезны против дешёвой рабочей силы стран третьего мира (по данным OWASP, цена приблизительно составляет $12 за 500 попыток), и некоторые из них могут быть противозаконны в определённых странах (см. OWASP Guide To Authentication). Если вам всё-таки необходимо использовать капчу, используйте reCAPTCHA, так как она по определению трудна в оптическом распознавании (используются уже нераспознанные сканы из книг) и старается изо всех сил быть user-friendly.
Лично я нахожу капчи раздражительными. Я использую их только в крайнем случае, когда пользователь превысил все мыслимые рубежи неудачных попыток ввода логина/пароля. Это происходит с приемлемой редкостью, и повышает безопасность в целом.
Хранение / проверка паролей
Вероятно, что после всех массовых взломов и утечек данных в сеть, которые мы наблюдали в последние годы, не для кого не секрет, но я повторюсь: не храните пароли в вашей базе данных незашифрованными. Базы данных пользователей взламываются, утекают или подбираются SQL-инъекциями, и если вы храните их в виде простого незашифрованного текста - это мгновенный game over всей вашей системе безопасности.
Но если вы не можете хранить пароль, то как же вы проверите, что комбинация "логин + пароль" верна? Ответ: хешировать используя функцию формирования ключа. Всякий раз, когда создаётся новый пользователь или изменяется его пароль, вы берёте пароль и прогоняете его через ФФК, такую как bcrypt, scrypt или PBKDF2, изменяя обычный текст пароля ("ХренВыМеняВзломаете666") в длинную хаотично выглядящую строку, которую куда более безопасно хранить в вашей базе данных. Чтобы проверить пароль на подлинность при попытке ввода, вы берёте введённый пользователем пароль и также прогоняете его через ФФК, в этот раз вставляя соль, и сравниваете полученный hash с тем, что храниться в базе данных. bcrypt и scrypt хранят хэшированный пароль уже с солью. Прочитайте данную статью (на англ.) на sec.stackexchange , если хотите копнуть глубже.
Причина, по которой мы используем соль, в том, что хэширования самого по себе не достаточно -- она необходима для защиты хэша от радужной таблицы. Соль предотвращает присваивание двух одинаковых паролей одному и тому же хэшу, что спасает от сканирования базы данных в один прогон, если атакующий пытается подобрать пароль.
Не следует использовать криптографический хэш для хранения паролей, так как выбираемые пользователями пароли не достаточно сильны (то есть обычно не содержат достаточно энтропии) и подбор пароля может быть выполнен в относительно небольшой промежуток времени атакующим, имеющим доступ к хэшам. Вот почему используются ФФК. Они значительно удлиняют ключ, то есть каждое угадывание пароля, которые производит атакующий, заставляет его использовать итерацию через алгоритм хэширования несколько раз (скажем 10000 раз), что делает атаку медленнее в 10000 раз.
Данные сессии - "Вы вошли как KyCoK_3a6oPa_123"
Как только сервер сравнил логин и пароль с записями базы данных и нашёл совпадение, системе нужен способ запомнить, что браузер был авторизован. Это факт должен храниться исключительно на стороне сервера.
Если вы не знакомы с тем, как работают данные сессии: одна случайно
сгенерированная строка сохраняется как куки с датой истечения, затем
используется как ссылка на набор данных - данные сессии, которые
хранятся на сервере. Если вы используете MVC фреймворк - не
волнуйтесь, об этом несомненно уже позаботились.
Если это возможно, убедитесь, что куки сессии имеют флаг безопасности и флаг HttpOnly перед тем, как отправлять их на сервер. Флаг HttpOnly даёт немного защиты против чтения путем межсайтового скриптинга. Флаг безопасности гарантирует, что куки будет отправлен только с помощью HTTPS, что защитит вас от перехвата пакетов. Значение куки должно быть непредсказуемым. Если куки начинает ссылаться на более несуществующую сессию, его значение должно быть изменено незамедлительно, что предотвратит session fixation
Часть вторая: как оставаться в системе или печально известная кнопка "Запомнить меня"
Постоянные Cookie-файлы (отвечающие за функцию "запомнить меня") находятся в зоне риска. С одной стороны они целиком и полностью безопасны, как и обычные логины, в случае, если пользователь понимает как с ними обращаться; с другой стороны, они могут представлять большую угрозу для безопасности у неопытных пользователей, которые могут использовать их на компьютерах в общественных местах и после забыть выйти из системы или вовсе не знать что такое файлы cookie и как их удалить.
Лично мне нравятся сохраненные логины на веб-сайтах, которые я регулярно посещаю, но я знаю как с ними обращаться. Если вы уверены, что ваши пользователи тоже в курсе, то можете с чистой совестью использовать сохраненные логины. Если нет - что ж, тогда можно философски заключить, что пользователи, которые легкомысленно относились к конфиденциальности своих учетных данных и были взломаны, виноваты сами. Конечно, мы также не ходим по домам пользователей и не срываем весь этот позор в виде приклеенных к мониторам листочков с логинами и паролями.
Разумеется, некоторые системы не могут позволить себе взлом ни одного аккаунта. Для них не стоит использовать сохраняемые логины.
Если вы все же решили использовать cookie-файлы для сохранения логинов, вот что вам следует сделать:
Во-первых, уделите немного времени на прочтение статьи от Paragon Initiative по этой теме. Вам нужно понять кучу разных вещей, а она отлично объяснит каждую.
Просто напоминаем об одной из самых распространенных ошибок. НЕ ХРАНИТЕ КУКИ ЛОГИНА (ТОКЕН) В СВОЕЙ БАЗЕ ДАННЫХ, ХРАНИТЕ ТОЛЬКО ИХ ХЭШ. Сохраненный login token эквивалентен паролю, так что если руки хакеров дотянутся до вашей базы данных, они могут использовать token, также, как если бы у них была комбинация логина и пароля, поэтому используйте login token при сохранении. Поэтому используйте хэширование (согласно https://security.stackexchange.com/a/63438/5002 слабый хэш отлично служит этой цели) когда храните login tokens.
Часть третья: использование секретных вопросов
Не используйте "секретные вопросы". "Секретные вопросы" -- антипаттерн безопасности. Ознакомьтесь с документом по четвёртой ссылки из раздела ОБЯЗАТЕЛЬНО ДЛЯ ПРОЧТЕНИЯ. Вы можете спросить Сару Пэйлин о том случае, когда её почтовый ящик на Yahoo! был взломан во время прошлой предвыборной кампании, потому что её секретный ответ был "Wasilla High School"!
Даже если пользователи сами вводят секретный вопрос, велика вероятность, что они выберут:
Вопрос из "стандартного набора", как девичье имя матери или имя любимого питомца
Простая информация, которую может раздобыть любой желающий из их личного блога, профиля LinkedIn или тому подобного
Любой вопрос, ответ на который получить перебором легче, чем подобрать пароль. А именно: любой вопрос, который вы только можете вообразить.
Вывод: секретные вопросы по существу не безопасны в фактически всех своих формах и проявлениях и не должны быть применены для аутентификации по любой причине.
Настоящая причина, почему секретные вопросы вообще существуют в том, что они так удобно экономят деньги на звонках в тех. поддержку с просьбой выслать код реактивации. Это цена безопасности и репутации Сары Пэйлин. Стоит того? Уверен, что нет.
Часть четвёртая: восстановление забытого пароля
Я уже объяснял, почему вы не должны использовать секретные вопросы для восстановления забытых/утраченных пользователем паролей; также, безусловно, не стоит отправлять пользователям их пароли по электронной почте. Есть по крайней мере две широко распространенных уловки, чтобы избежать этого.
Не менять забытый пароль на надежный автогенерируемый, - подобные
пароли сложно запомнить, а это значит, что пользователь или изменит
его, или напишет на желтом стикере и приклеит внизу монитора. Так что
вместо того, чтобы устанавливать новый пароль, позвольте
пользователям выбрать, - что они и хотят сделать.
Всегда хешируйте пароль/token в базе данных. Опять же, это другой
пример эквивалента пароля, так что он ДОЛЖЕН быть хеширован на
случай, если хакер доберется до вашей базы. Когда потребуется
утраченный пароль, оправьте пароль в виде просто текста на
электронную почту пользователя, затем хешируйте и сохраните в базе
данных, -- и затем избавьтесь от оригинала.
Последнее замечание: будьте уверены, что ваш интерфейс для ввода "забытого пароля" настолько же безопасен, насколько и ваша форма входа. Хакер просто использует ее, вместо того, чтобы получить доступ. Убедитесь, что генерируете очень длинный "забытый пароль" (например 16 символов разного регистра) это хорошо для начала, но не забудьте добавить схемы защиты, используемые в форме входа.
Часть пятая: проверка силы пароля
Во-первых, вам нужно прочитать эту маленькую статью, чтобы узнать реальное положение вещей: 500 самых распространённых паролей (на англ.)
Ладно, быть может это и не самый каноничный лист самых распространённых паролей во всех системах везде за всё время, но он хорошо демонстрирует нам то, как безосновательно пользователи подходят к выбору пароля, если к ним не предъявлены требования. Плюс, этот лист до боли похож на лист паролей, полученных в результате анализа недавно украденных аккаунтов.
Итак: без требований к минимальной длине пароля, 2% пользователей используют один из топ-20 самых популярных паролей. Вывод: если у хакера будет словарь из всего лишь 20 паролей, каждый 50-ый аккаунт на вашем веб-сайте будет взломан.
Избежать этого можно установив на сайте минимальный порог энтропии пароля. Специальная публикация национального института стандартов и технологий содержит набор очень хороших предложений. Так, скомбинировав словарь и анализ раскладки клавиатуры, можно отбросить 99% всех ненадёжных паролей на уровне 18-ти битной энтропии. Просто высчитывание силы пароля и вывод визуального индикатора силы пароля -- это хорошая практика, но недостаточная. Если пользователя не заставить, он скорее всего проигнорирует рекомендации.
Рекомендуем посмотреть данный комикс, чтобы отбросить стереотип о трудной запоминаемости паролей с высокой энтропией.
Часть шестая: все больше и больше - или предотвращение скоропалительных попыток входа
Для начала давайте ознакомимся с цифрами
Если нет времени на ознакомление с данными статьи, вот краткая информация:
Практически моментально можно взломать слабый пароль, даже если взламываете его при помощи счёт
Практически моментально можно взломать буквенно-цифровой 9-ти символьный пароль, если он не чувствителен к регистру знаков
Практически моментально можно взломать сложный символьно-буквенно-цифровой, верхне- и нижнерегистровый, если в его составе менее 8-ми символов (обычный компьютер может подобрать пароль, состоящий из 7 символов за считанные дни или даже часы)
Однако придется потратить уйму времени, чтобы взломать даже шестизначный пароль, если у вас стоит ограничение на одну попытку в секунду
Так что же мы уяснили из этих цифр? Ладно, много, но выделим главное: не так уж сложно защититься от быстрых бесконечных попыток войти в систему (так называемых брут-форс). Но все не так просто, как кажется.
Короче говоря, есть три варианта эффективной защиты от брут-форса (и атаки по словарю, но учитывая, что вы уже используете политику сильных паролей, это больше не проблема):
Наличие капчи после N-го числа неудачных попыток (адски бесит и зачастую неэффективно - но я повторяюсь)
Блокировка аккаунта и требование подтверждения адреса электронной почты после N-го числа неудачных попыток (DoS-атака только и ждёт, чтобы это случилось)
И, наконец, защита логина -- то есть установка временного промежутка между попытками ввода пароля после N неудачных попыток (да, DoS атаки всё ещё возможны, но происходить они будут гораздо реже и с ними будет легче справиться)
Лучшая практика №1: короткие промежутки времени, увеличивающиеся с ростом числа неудачных попыток входа, например:
1 неудачная попытка - нет задержки
2 неудачные попытки - 4 секунды
3 неудачные попытки - 8 секунд
4 неудачные попытки - 16 секунд
и так далее
Для DoS атаки такая система очень неудобна, так как каждый последующий промежуток времени блокировки больше всех предыдущих вместе взятых.
Уточняем. Задержка - это не задержка перед возвратом ответа браузера.
Это больше похоже на таймаут или рефактерный период, в течение которого
попытки войти в какой-либо аккаунт или с определенного IP- адреса
вовсе не принимаются. То есть правильные credentials или как их там,
не вернутся я так понимаю после успешного входа в систему. и неверные
кредентиалы не увеличат задержку.
Лучшая практика №2: среднесрочная задержка, которая наступает после N неудачных попыток, например:
1-4 неудачные попытки - нет задержки
5 попыток - 15-30 минут задержка
Для DoS взломать эту схему сложно, но, безусловно, выполнимо. Также стоит отметить, что такая задержка раздражает авторизованных пользователей. особо забывчивые вас возненавидят.
Лучшая практика №3: совмещение этих двух подходов, либо фиксированное короткое время задержки, наступающее после N числа неудачных попыток, например:
1-4 неудачных попытки - нет задержки
5+ - 20 секунд задержки
или увеличение времени задержки с ростом числа неудачных попыток входа, например:
1 неудачная попытка - 5 секунд
2 неудачные попытки - 15 секунд
3+ неудачные попытки - 45 секунд
Эта последняя схема была заимствована из списка лучших предложений OWASP (ссылка 1 из списка "ОБЯЗАТЕЛЬНО ДЛЯ ПРОЧТЕНИЯ") и должна рассматриваться как лучшая практика.
По правилу большого пальца, я бы сказал, что чем надежнее ваш пароль,
тем меньше вам придётся мучить пользователей задержками. Если вы требуете
надежный (с учетом регистра символов + требование наличия букв и цифр)
9+ символов в пароле, можно предоставить пользователям 2-4 попытки
ввода пароля до включения блокировки.
Для DoS взломать такую систему будет очень не просто. И наконец, всегда разрешайте сохраняемые (куки) логины (и/или форму, защищённую капчей) для входа, так у авторизованных пользователей не будет задержек, пока продолжается атака. Таким образом для DoS задача из очень сложной превращается в экстремально сложную.
Кроме того есть смысл делать более совершенную защиту для аккаунта администратора, так как те являются наиболее привлекательными для взломщиков.
Часть седьмая: распределённая брут-форс атака
Попутно отметим, что более продвинутые атакующие будут пытаться обойти задержку логина при помощи "распределения активности":
Распределять попытки ботнетом во избежание маркировки IP
Вместо того, чтобы брать одно пользователя и пробовать на нём 50 000 самых распространённых паролей (чего они не могут из-за задержек), они возьмут самый распространённый пароль и попробуют его на 50 000 пользователей. Таким образом, они не приблизятся к лимиту неудачных вводов логина / пароля и их шансы возрастут, так как самый популярный пароль намного легче встретить, чем пароль номер 49 995.
Разделят запросы к каждому логину интервалом в 30 секунд, чтобы не попадаться на радары.
Так что лучшей практикой будет учитывать суммарное число неудачных попыток входа во всей системе и использовать среднее их число как лимит для каждого отдельного пользователя.
Слишком абстрактно? Позвольте перефразировать:
Скажем, ваш сайт имеет средний показатель 120 неудачных входов в день за последние 3 месяца. Взяв этот период за основу вы можете умножить глобальный лимит на 3 -- т.е. 360 неудач в день. Теперь, если если это пороговое значение будет превышено (а лучше мониторьте изменение среднего показателя), активируйте всесистемную задержку входа для ВСЕХ пользователей (опять же, исключая вход с куки и вход с капчей).
Также ознакомьтесь с вопросом на SO, включающим больше информации о том, как обойти подводные камни в борьбе с распределёнными брут-форсами.
Часть восьмая: двухфакторная аутентификация и провайдеры аутентификации
Credentials могут быть скомпрометированы либо эксплоитами, либо их записью и утратой носителя, кражей ноутбука, а могут быть введены на фишинговом сайте. Логины могут быть дополнительно защищены двухфакторной аутентификацией, которая использует другие методы, такие, как одноразовые коды, полученные чрез телефонный звонок, SMS сообщения, приложения или электронный ключ. Некоторые провайдеры предлагают двухфакторную систему.
Аутентификация может быть возложена на single-sign-on сервис, где другие провайдеры собирают credentials. Это переправляет проблему доверенной третьей стороне. Google, Yandex и Twitter предоставляют стандартный набор SSo услуг, а FaceBook предоставляет схожее запатентованное решение.
ОБЯЗАТЕЛЬНО ДЛЯ ПРОЧТЕНИЯ о веб-аутентификации (на англ.)
OWASP Guide To Authentication / OWASP Authentication Cheat Sheet
Dos and Don’ts of Client Authentication on the Web (very readable MIT research paper)
Wikipedia: HTTP cookie
Personal knowledge questions for fallback authentication: Security questions in the era of Facebook (very readable Berkeley research paper)