Pull to refresh

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

Reading time9 min
Views54K

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

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

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

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

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

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

Junior level

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

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

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

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

Аутентификация? Авторизация? Идентификация? В двух словах расскажите что за что отвечает.

Часто эти понятия путают, поэтому давайте кратко:

  • Идентификация - это проверка что такой пользователь/логин/email существует в системе.

  • Аутентификация - проверка связки логина и пароля, то есть проверка на то, не выдаёт ли пользователь себя за другого человека.

  • Авторизация - проверка прав доступа пользователя к внутренним ресурсам.

Крутую статью с примерами на енотах посоветовали в комментах.

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

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

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

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

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

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

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

Данные статистики говорят о том, что 30-40% организаций хранят пароли в plain text. И в 2018-ом даже Twitter с его 330+млн пользователей смог. Ух.

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

Нужно ли проверять вводимые данные не только на 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 токеном. Это гарантирует отправку формы тем же клиентом, что её и отобразил.

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

update : Меня в комментариях резонно тыкнули в то, что уже лет пять проблема как фактически не актуальна, т.к. существует и поддерживается аттрибут SameSite. Резонно. Остаётся лишь вопрос к старым браузерам, где этой поддержки нет. Тут и тут есть описание проблемы и её решения.

update2 : Так же CSRF уязвимость не имеет силы при использовании JWT токенов, т.к. сам app ставит их при осуществлении запросов.

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

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

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

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

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

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

Если взять ПК с AMD R9 290X, то он сможет сгенерить 172 миллиарда md5/сек, 11 миллиардов SHA-256/сек, 797 миллиона SHA-512/сек и 1.3 миллиона PBKDF2 (8192 итераций и соль, 20 байт на выходе).

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

Так же, кроме соли еще существует и понятие перца. Если соль может храниться рядом с данным пользователей, то перец должен храниться где-то в другом месте (об этом ниже).

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

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

Правильный ответ тут - не нужно изобретать криптовелосипеды, если вы не бородатый математик! Слишком просто допустить критическую ошибку в алгоритме и снизить стойкость. Хорошо, если вы сможете привести пример несложного шифрования через 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” алгоритм, который так же не будет известен хакеру - этим вы ещё больше снижаете риск взлома хэшей, т.к. соль будет храниться в двух местах. Усложняя - можно (и нужно) комбинировать эти два метода и переменную брать, допустим из переменных окружения. 

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

Дополнительные материалы

1) Годные советы по функционалу окна аутентификации

Выводы

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

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

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

Tags:
Hubs:
Total votes 69: ↑59 and ↓10+49
Comments211

Articles