Как бы вы реализовали форму аутентификации на сайте? Вопрос для собеседования на Junior/Middle/Senior?

от автора

В свете исследования «Веб-разработчики пишут небезопасный код по умолчанию« мне подумалось, что именно так может звучать один из базовых вопросов на собеседовании с точки зрения проверки знания web-разработчика от уровня Junior до Senior.

Тема с одной стороны в общем-то простая, а с другой — многогранная. Можно сделать “на коленке”, а можно и “по-взрослому” —  зависит от знаний конкретного девелопера и технического задания. Ну и не привязывается к конкретному языку. Что nodejs, что .net, что PHP — на ответы это не влияет. Ну и отлично же! Давайте попробуем.

Я попытался разбить вопросы на три уровня. Каждый следующий уровень обязан включать все вопросы выше, т.е. уровни и вопросы отсортированы от простых к более сложным.

Как бы вы ответили на конкретный вопрос? Попробуйте проверить себя и потратить пару минут на обдумывание прежде чем читать ответ.

Восклицательным знаком ⚠ помечены вопросы, на которых можно «засыпаться» и оставить плохое впечатление о себе у интервьюера. Так же я позволил себе добавить еще пункты, которые подразумевают «Регистрацию», но по касательной. Многие ответы обрамил ссылками, которые помогут разобраться чуть глубже в конкретном вопросе, думаю будет полезно.

И так, за вёсла!

Junior level

Нужно ли скрывать вводимый пароль на странице?

Конечно. Никто не хочет, чтобы кто-то подсматривал пароли из-за плеча, верно? У input есть специальный атрибут password для этого. Это очевидно, но это и первый пункт. Дальше будет сложнее, обещаю 🙂

Должны ли валидироваться поля для ввода на клиентской стороне?

Должны. Как минимум на пустые значения. Отправлять пустую форму на сервер плохая идея. Во-первых, зря нагружает бэкенд, во-вторых страдает юзабилити — вы заставляете пользователя ждать там, где можно обойтись и без запросов на сервер.

Запрос с данными формы должен идти через GET или POST? 

Правильнее делать используя метод POST. Ничего вам не мешает сделать это любым другим методом, однако правильнее всего данные формы слать именно через POST. Изначально этот тип запроса был спроектирован не идемпотентным. Это значит, что отсылая его вы не можете гарантировать, что последствия его выполнения будут одинаковыми, если вызвать его несколько раз подряд. Поэтому в случае обрыва соединения браузер переспросит вас хотите ли вы заново отослать эту форму (вы наверняка видели такую хотя бы раз). Все GET запросы же перепосылаются браузерами без подтверждения.

Так же тут стоит упомянуть, что все данные запаковываются в body, который прикрепляется к запросу, что может помочь от утечек как в системе логирования на стороне сервера так и по пути к нему.

Почитать можно тут и на стеке

Сохраните ли вы в пароль при регистрации «как есть» в текстовом виде (plain text)?

Если да — это непрофессионально и даже преступно. Пароли должны быть зашифрованы стойким алгоритмом по ключу, идеально — если они хэшированы.

Хэширование — это преобразование данных без возможности их обратного восстановления. И кодирование Base64 тут не подойдёт, как почему-то решили некоторые ребята из исследования в начале статьи. В случае, если вашу базу выкрадут злоумышленники — у них окажутся все пароли ваших пользователей. В случае же шифрования или хэширования — им придётся изрядно дополнительно попотеть чтобы завладеть паролями даже с учётом слитых данных…

Данные статистики говорят о том, что 30-40% организацией хранят пароли в plain text. Ух.

Почитать можно тут и на хабре

Нужно ли проверять вводимые данные не только на UI стороне, но и на сервере? Является ли это ненужной/двойной работой?

Главное правило веб разработчика — не доверяй клиенту (браузеру). Все данные/поля могут быть отосланы/подменены другими инструментами, поэтому вам нужно обязательно делать полный цикл проверки на сервере.

Почитать развёрнутый ответ можно на стеке

Нужна ли валидация на сложность пароля или можно позволять пользователю иметь пароль любой сложности?

Заставлять пользователя придумать пароль в соответствии с правилами — безусловно здравая идея, т.к. слишком простые пароли легко подбираются путём перебора по словарю. Но тут важна грань, т.к. знаю реальный пример, когда правила пароля настолько сложные, что пользователи потом хранят эти пароли в текстовых файлах на рабочих столах, т.к. запомнить их нереально. А это еще хуже 🙂 Получается, заботились о безопасности, а по факту родили новый вектор атаки.

Хорошая статья про границы политики паролей есть на хабре

Middle level

Соединение защищено по https?

Если нет, то у меня для вас плохие новости — ваш логин и пароль может быть легко перехвачен по пути от вашего браузера к серверу. Это может быть как целенаправленная атака, так и “пассивный сниффинг данных“ публичными Wi-Fi, на уровне провайдера или даже на уровне уязвимого роутера, которым вы пользуетесь. Еще лет пять назад встречались сайты, где форма оплаты могла быть на https, а весь сайт — по http и в целом это работало безопасно (т.к. во время платежа на клиент не сохраняется никаких данных, которые впоследствии могут быть перехвачены по http каналу), однако сайты без https сегодня — это уже моветон и первый признак того, что такому сайту свои данные доверять нельзя.

оффтоп

Кстати, буквально на днях выпустил пост о «SSL/TLS/Ассинхронном шифровании на пальцах» у себя на тг канале, поэтому если интересна тема — вэлкам!

На тему вопроса есть хороший ответ на стеке

Нужно ли при неудачной попытке регистрации писать, что такой пароль уже существует в системе?

Конечно нет. Вы должны выдавать МИНИМУМ информации всего, что касается безопасности. Есть шуточная версия в виде сообщения «такой пароль уже используется пользователем %username%». Шутки шутками, но возможно где-то и внедрили, за более чем тринадцать лет опыта работы и не такое встречал.

На aws к слову максимальный уровень — после ввода логина, сразу же запрашивают пароль + второй фактор (2fa), т.е. злоумышленник даже не сможет узнать какой именно параметр не подошёл.

Если попытка входа была неудачная — нужно ли логировать данные формы для дальнейшего изучения проблемы?

Нельзя. Максимум — это логин (а лучше хэш логина). Пароль — нельзя. Было уже множество громких инцидентов когда у крупных сервисов ломали системы логов и вытаскивали пароли, которые логировались в plain text (логировался весь реквест).

Примеры взломов можно глянуть тут и тут

Если хэшировать в базе пароль, то как? Каким алгоритмом? 

Хорошо, если мидл назовёт парочку, допустим md5 или sha-1/sha-256. Вопрос чем они плохи — уже скорее сеньорский левел, обсудим это ниже. Однако считаю, что для мидла и такой ответ пойдёт. Также плюсом для собеседующегося будет рассказ о том как именно нужно хэшировать — т.е. с солью. SALT — это что-то, что добавляется к паролю чтобы уменьшить риск его обратного преобразования в случае утечки данных. Многие популярные пароли уже находятся в базах (так называемые радужные таблицы или Rainbow tables) и хеши к ним подобраны, поэтому реверснуть какой-нибудь md5 без соли — дело несложное.

Вот тут хорошая статья с хабра о хешировании паролей

Senior level

Каким еще способом можно обезопасить форму? Что такое CSRF токен и имеет ли смысл  его добавлять?

Это вопрос на самом деле в целом на понимание вида уязвимости CSRF (Cross-Site Request Forgery). Сеньор должен знать и понимать как это работает как со стороны имплементации, так и со стороны взламывающей стороны (хакера).

Безусловно, запрос должен посылаться с CSRF токеном. Это гарантирует отправку формы тем же клиентом, что её и отобразил.

Подробности тут

Что такое двухфакторная авторизация (2fa) и нужна ли?

Вопрос для сеньора, однако в целом мидловский, т.к. 2fa уже прочно вошла в широкие массы и будет плохим признаком, если вы ответите в духе wtf. 

Для серьёзных сервисов — 2fa is a must. Существенно снижает риск зайти в вашу учётку злоумышленником даже зная ваши логин и пароль. 2fa — это «второй фактор», обычно некий токен, который выдаётся через ваш телефон (приложение или же смс), но не ограничивается им. Могут быть и более безопасные варианты в виде usb флешек, блютус девайсов, биометрических сканеров и тп.

Почитать о 2fa можно тут

Как бы вы хранили пароль в базе? Хешированным? Солёным? Поподробнее пожалуйста!

Пароль должен быть хеширован, притом важен алгоритм. md5 и sha-1 — уже не подходят в текущих реалиях и помечены как небезопасные (RFC 6151,RFC5246). Используйте bcrypt, PBKDF2 или SHA-256. 

Что по соли — она может быть одна для всех пользователей, но в идеале — уникальная для каждого пользователя, т.е генерируется из данных самого пользователя. Плюсом будет, если упомянете тут о радужных таблицах.

Хорошая детальная статья тут

Сможете ли сами написать алгоритм для хранения пароля?

Правильный ответ тут — не нужно изобретать криптовелосипеды, если вы не бородатый математик! Слишком просто допустить критическую ошибку в алгоритме и снизить стойкость. Хорошо, если вы сможете привести пример несложного шифрования через XOR или расскажете про AES256 и для чего нужен вектор инициализации (IV). 

Ну и немного вопросов для Senior+

Что делать в случае коллизий паролей при хэшировании?

Коллизии существуют для большинства хеш-функций, но для «хороших» функций частота их возникновения близка к теоретическому минимуму. В целом — чем длиннее хэш, тем меньше шанс этой коллизии. Именно md5 и sha-1 не обеспечивают этой длины в сегодняшнем мире. Так же можно упомянуть про двойное хеширование и метод цепочек — внутрянку знать не обязательно, просто знать, что методы есть.

Если принято решение шифровать пароль, то какой тип шифрования выберете? Почему?

Вопрос на знание отличий симметричного vs ассиметричного типа. Хорошо бы понимать разницу. Ответом тут на самом деле будет  “it depends”, зависит от системы взаимодействия. Обычно, используют симметричное, т.к. ассиметричное подразумевает валидацию второй стороной, которой скорее всего не будет. Плюс симметричное шифрование быстрее. Неплохо бы так же дополнить, что шифрование не замена хэшированию и должно применяться только в крайних случаях для кейсов подобно обсуждаемому нами.

Рекомендации по теме «Hashing vs Encryption» можно найти прямо в cheetsheets OWASP’a

В связи с предыдущим вопросом — одинаков ли по стойкости 128-битный ключ для симметричного и ассиметричного способа?

Нет, не одинаков. Природа ассиметричного шифрования подразумевает, что ключ должен быть намного длиннее. Эквивалент 128 битного симметричного ключа равен 2048 ассимметричного. Хорошо бы тут еще затронуть про плюсы и минусы каждого из подходов и паттерны применения (расскажите про SSL/TLS и как он устроен)

В дополнение — хороший ответ на qna хабра тут

Хорошая ли идея вынести аутентификацию на openid?

Безусловно да, если на сайте множество поддоменов и сервисов — это позволит упростить коммуникацию групп бэкендов/сервисов, а так же фрагментирует точки отказа в случае их возникновения. Т.е. если ляжет авторизация, все остальные узлы сервиса скорее всего будет работать, т.к. логически вынесены. Получится, что модуль аутентификации не размазан по множеству бэкендов. Плюс это более расширяемое решение и добавляет изолированности этой важной функции.

Если на аутентификацию будет один из векторов атаки — это не затронет остальные компоненты системы. К слову это частая точка для ддоса, т.к. обычно грамотная аутентификация построена на «долгих» алгоритмах и кушает и память/cpu, а значит может легко положить всю систему, если та гвоздями приколочена к основной группе сервисов. Хорошо бы еще упомянуть и про Oauth 2.0 — это второй по популярности протокол.

Что есть Rate Limiter и каким боком оно нам к форме аутентификации?

Это уже немного вне темы сабжа, но и уровень всё же со звёздочкой — добавим в тред 🙂 Rate Limit (или Throttling) — это механизм ограничения запросов по какому-то сценарию. В случае брутфорса или же ддоса — позволит отсекать вредных клиентов и не аффектать «хороших». Все большие ребята следят за этим. Часто авторизацию прячут (как и весь сайт) за тем же Cloudflare, который берёт это на себя эту функцию. Rate Limiter может быть и кастомным и быть реализован на уровне самого приложения. К примеру в виде “не принимать больше n запросов per period от такого-то пользователя с такой-то ролью”. Есть такие опции и у тех же Lambda functions от AWS, там прямо в интерфейсе можно прописать нужные цифры.

Тут можно найти общау инфу и про алгоритмы. Ну и классная статья от Яндекса об их пути рейт лимитера

Где хранить соль и как её формировать?

Обычно, соль хранится где-то рядом с паролем. Однако в случае, если злоумышленник сможет слить базу — соль станет ему доступна. Поэтому к соли еще хорошо бы добавлять либо какую-то константу, которой нет в бд, либо проводить данные через какой-то “black-box” алгоритм, который так же не будет известен хакеру — этим вы ещё больше снижаете риск взлома хэшей, т.к. соль будет храниться в двух местах. Усложняя — можно (и нужно) комбинировать эти два метода и переменную брать, допустим из переменных окружения. 

Так же важно добавлять соль после пароля, а не до. Причиной этому является одной из свойств хэширования — поточное преобразование. В случае, если соль добавлется перед паролем — злоумышленник скорее всего сможет найти соль, учесть её и вычислять уже хэши без учёта этой самой соли.

Выводы

Как мы видим — одно и то же задание может быть сделано как джуниором, так и высококлассным специалистом и результаты будут весьма разные. Позволю себе вставить пять копеек к статье об исследовании в начале сабжа : глупо ожидать серьёзного уровня реализации за 200 евро (а уж тем более за 100) на фрилансе, однако и хранить в Base64 определенно точно не стоит, если вас попросили обеспечить безопасность паролей. Нужно объяснять заказчику тезис трёх основополагающих свойств результата работы : Быстро/Качественно/Дёшево — выбрать можно только два из них.

Пожалуй, это основные моменты, которые пришли на ум. Если что-то пропустил или где-то не прав, вэлкам в комментарии 😉

ps. Если вы так же любите айтишечку как и я — буду рад видеть вас на своём Telegram канале.


ссылка на оригинал статьи https://habr.com/ru/post/668104/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *