Pull to refresh

Comments 163

Заставляем клиента делать сложные пароли.
Самое популярное, но не самое лучшее решение проблемы с простыми паролями. Практически на каждом 3-м сайте пользователям приходиться регистрироваться и у многих сайтов различаются требования к паролям. Пользователи начинают путаться куда-какой пароль они вбивали. Какой длины, заглавные, строчные, символы. Они в конце концов везде вводят один и тот же пароль, а когда надо добавляют в конце пару цифр.
Это очень сложно для не далеких, да и для вполне приближенных к IT, пользователей. Конечно, лучшего решения пока нет. Двухфакторная аутентификация (к примеру SMS) по проще чем сложный пароль, но не всегда удобна, да и дороже
согласен, но и разрешать пользователям пароли вида:123456,qwerty,password тоже нельзя. Если и не делать сложных валидаторов, то хотя бы проверять в топе популярных паролей.
Предупредите пользователя, выведите подсказку о слабом пароле. Если данные ему важны, он одумается.
Ниже по коментам уже предложили несколько вариантов:
— помогать генерить сложный пароль
— ввести подсказку-валидатор
— ничего не делать(юзер сам дурак)
— вообще забить на регистрацию и делать через OpenID
Можно. Что за паранойя? Я часто регистрируюсь на куче ресурсов, которые нужны один раз в жизни. Каждому сделай пароль, и не просто пароль, а чтобы были и строчные и заглавные и цифры и спецсимволы, да ещё и если что-то не так, тут же тычет носом в ошибку.

Мне просто надо зайти под user1234 с паролем 123456 и мылом с 10minutemail, сделать то, что мне надо и забыть про ресурс, как про страшный сон. Но почему-то каждый сайт мнит себя минимум банком и воображает, что имеющиеся в профиле данные «Вася Пупкин» и «forspam@mail.com» могут смертельно навредить юзеру, если попадут в чужие руки.

Что за паранойя?

UPD: Забыл добавить, что ещё и верификацию емейла часто делают. И пока письмо не пришло, сделать вообще ничего нельзя. Сотни, если не тысячи человеко-часов потрачены на какую-то ерунду.
UFO just landed and posted this here
> то массовый взлом грозит неприятностями уже ресурсу, например волной спама

Это проблема ресурса, а не пользователей. В банкомате тоже деньги лежат, но ведь у каждого банкомата не поставлено по охраннику с автоматом.
Отличный вариант уйти от паролей — OAuth авторизация с несколькими вендорами. У многих есть профили в гугле, фейсбуке или вконтакте, так зачем напрягать пользователя на создание нового?
Возможно, вы имели ввиду OpenID аутентификацию? Не знаю, вероятно меня сейчас закидают тухлыми помидорами, но я всегда испытываю некоторый дискомфорт, когда мне предлагают авторизацию через OpenID, с одной стороны сервис как бы отдает на откуп аутентификацию третьим лицам (провайдеру OpenID) (интересно, спецслужбы тоже являются провайдерами OpenID? :)), а с другой стороны это получается как мастер-пароль, как ключ от всех дверей, как игра ва-банк — если уплывет этот один единственный золотой ключик, то Буратино вы теряете все (по той же причине меня очень сильно напрягает, например, использование единого входа на всех сервисах Google и др)…
А почтовый ящик на всех сервисах какой указываете? Так как что вы указываете google oauth что email если украдут пароль от ящика, то восстановят на него пароли
Я указываю разные ящики или вообще одноразовые, в зависимости от сервиса, при возможности/необходимости использую дополнительные способы/механизмы аутентификации/смены пароля (смс и проч)…
Я не сильно в теме — пока не доводилось сталкиваться, но судя по всему это похожие, но разные вещи.

Судя по беглому осмотру википедии OAuth — это завязка на апи по токену одного сервиса, а OpenID — более универсальная вещь, без привязки к конкретному вендору.

Вот гуглу я доверю стороннюю аутентификацию, а чтоб он не ушел — уже акк гугла завязан на мобильный, с сильным паролем, секретными ответами и прочими. Таким образом не нужно бояться за сотни акков, обезопасив всего один.
Различие у OAuth и OpenID прежде всего в тех задачах, которые они решают.
OAuth решает задачу доступа к API одного сайта от имени пользователя, зашедшего на другой сайт.
OpenID просто позволяет узнать, кто именно зашел на сайт.

Но так получилось, что OAuth часто используется для задач аутентификации, а не авторизации. Для этого запрашивается разрешение на просмотр, к примеру, email — после чего по этому email и проходит аутентификация.

Насколько я знаю, вконтакт предоставляет только OAuth, так что вк-аутентификация с его помощью выглядит оправданной. А вот гугл предоставляет как OAuth, так и OpenID — поэтому когда я вижу гуглоаунеттификацию через OAuth, на ум приходит сравнение с применением тяжелой артиллерии по низколетящим целям малого размера.
Ха Ха. Рассказать про безопасность OAuth?
Если есть что рассказать, то конечно сделайте это!
Поддерживаю. Расскажите.
homakov.blogspot.com там уже все рассказано
уж послал, так послал…
«Пароли — в виде хешей с солью» — это хорошо. Главное, соль хранить как-то отдельно от хэша. А то когда в таблице auth, к примеру, лежит и хэш и пароль возникают мысли «А зачем»? :-)
«А зачем» еще возникает, когда к примеру в одной онлайн игре хранят в открытом виде и логин и пароль. И при восстановлении пароля отсылают его на email 2 раза. Один раз бот через cron, второй для надежности сам модератор.
Ну это отдельный разговор, тож такое видел не раз. Очень помогало :-D
В том, что нельзя создать одну радужную таблицу, которая мгновенно взломает все пароли сразу, а для каждого пароля нужно будет тратить большое количество времени.
Еще хочется добавить
1) fail2ban
2) запрет авторизации под рутом
3) ddosDeflate
4) OTP
Вообще тема ddos достаточно обширная. Ее похорошему можно вынести в отдельный пункт. От целенаправленного ddos скорее всего сам не отобьешься, а вот чтобы не завалить себя хабраэффектом — это уже вопрос оптимизации приложения.
и ddosDeflate и fail2ban можно использовать и для защиты от пебора паролей в публичке
Ещё можно юзать такие правила на файрволе:

#TCP Filters ##
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ALL NONE -j DROP
#SYN+FIN
iptables -t mangle -A PREROUTING -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP
#SYN+RST
iptables -t mangle -A PREROUTING -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
#FIN+RST
iptables -t mangle -A PREROUTING -p tcp --tcp-flags FIN,RST FIN,RST -j DROP
#FIN w/o ACK before
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,FIN FIN -j DROP 
#PSH w/o ACK before
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,PSH PSH -j DROP
#URG w/o ACK before
iptables -t mangle -A PREROUTING -p tcp --tcp-flags ACK,URG URG -j DROP


5) Подписаться на рассылки/RSS безопасности вашего дистрибутива
И всех используемых языков, фреймворков, серверов, middleware etc
… а еще выучить английский, чтобы понимать, о чем там в этих рассылках вообще речь…
Там достаточно совсем базового английского. Но, ИМХО, английский, в любом случае, необходим для работы программистом или сисадмином.
2) запрет авторизации под рутом


Спорно имхо. Не могли бы Вы аргументировать этот пункт? Имхо вполне достаточно поставить любой антибрут и отключить парольную авторизацию.

Тогда как запрет авторизации под рутом заставляет нас либо
1) иметь действующий пароль от рута для локальной авторизации через su. Здравствуйте локальные брутеры после взлома Вашего пользователя. Так же здравствуйте всяческие подглядыватели через плечо при вводе пароля от su.
2) иметь sudo на сервере. Не самое простое и надежное средство для повышения привилегий. При нахождении уязвимости в sudo вообще даем атакующему возможность повысить привилегии при взломе любого незачрутенного сервиса, смотрящего в сеть.
Если авторизация по ключам -то смысла нет, вы правы.
Если по паролю — то для получения рутового доступа нужно подобрать логин пользователя с доступом к su (а он может быть не типовым, к примеру, joker), подобрать его пароль, потом подобрать пароль от рута.
+ к этому, если с сервером работает несколько человек, было бы логично давать им персональные доступы.

В целом, конечно, антибрутов то и правда достаточно.
Ну все упирается в то, что кто-то зачем-то может не отключать парольный доступ. Но это стоит делать просто по нулевому приоритету. Уж тем более для рута.
Тоже к пункту 3.7. Один сайт ругался при вводе пароля для регистрации — то символов в верхнем и нижнем регистре нет, то длина пароля короткая. В блокноте набрякал ему разного и побольше, скопивставил и… опять досада. Длина пароля не может быть более 20 символов.
Как вариант можно помогать самому пользователю сгенерить хороший пароль. Тут еще вопрос юзабилити, чтоб было и удобно, у просто, и самое главное безопасно.
Я не о юзабилити, а о безопасности. Но идея помочь генерить пароль на стороне клиента и не заставлять его выдумывать — это здорово.

Предлагаю добавить пункт о максимально разрешенной длине пароля. А именно, чтобы на стороне клиента не ограничивать длину пароля (до разумных пределов). Насколько длинным должен быть пароль, чтобы его нельзя было вычислить по хешированной сумме с солью, допустим, в ближайшие несколько лет?
Длина пароля никак не влияет на длину хэша. Тут вопрос стоит в том, чтобы выбрать безопасную хэш функцию и обязательно соль.
Если в двух словах: не используйте md5, используйте sha. Почитать можно например тут
Опять Вы о другом. Я говорю не о длине хэша, а о длине пароля. Нужно дать пользователю возможность при регистрации ввести пароль длиной, скажем, до 32 символов. Это никак не скажется на длине поля, где мы храним хешированную сумму (128 бит для MD5 или 160-256 бит для sha, если он Вам так нравится).
Если же пользователь ограничен по длине пароля, применяемые средства хеширования и соления не защитят от перебора.
латиница(большие и маленькие буквы), цифры и различные символы уже около 100 символов. Теперь для 10-символьного пароля все возможные варианты 100^10. Что-то я сильно сомневаюсь что перебор поможет.
Вопрос был не о том, сложно ли взломать 10-символьный пароль. А в том, зачем ограничивают максимальную длину пароля? Видел не один раз верхнюю границу — «ваш пароль слишком длинный, пароль должен быть от 8 до 16 символов». Зачем? Есть у кого какие мысли?
Некоторое время назад на хабре была статья habrahabr.ru/post/211645/, в которой обсуждали уязвимость bcrypt в плане его ограничения на длину хэшируемой строки. То есть, по крайней мере для некоторых алгоритмов, верхняя граница длины пароля существует. Но не 16-20 же символов?
Скорее всего, когда-то пароли хранились в базе в виде varchar(16). С тех пор и повелось.
Да, меня тоже раздражают такие ограничения. Paypal, например, не позволяет указать пароль длиннее 20 символов.
То есть сайт, который будет применять все правила из списка выше, но не даст ввести/вставить пароль длиннее 10 символов это безопасный сайт? Зачем вообще ограничивать длину пароля, тем более, как Вы справедливо заметили, длина пароля не влияет на длину хэша.
вы меня не так поняли, я лишь указывал, что перебор тут бесполезен. А ограничивать можно в пределах допустимых значений, врятли кто-то пароль больше 100 символов вводить будет. Тут вопрос только в валидаторе.
Не используйте ни MD5, ни SHA1.
Лучше всего использовать специализированные хеш-функции, типа PBKDF2 или scrypt.
По-моему, степень сложности пароля должна оставаться на совести пользователя, потому что это к безопасности сервера отношения не имеет.
Мне очень нравится подход, когда предупреждают, что пароль слабый, но не заставляют его менять на более сильный. Предупредил — дальше сам думай.
Согласен, пользователь сам себе злобный буратино, но о последствиях тоже надо думать. Потому как с украдеными акками пользователь побежит сразу к вам и будет требовать вернуть доступ. Но если это не является проблемой, то почему и нет?
Можно записывать силу пароля отдельным полем в таблицу. На безопасность оно повлияет не сильно — все равно при утечке таблицы все слабые пароли будут взломаны — но зато будет что ответить клиенту.
Кстати, это достаточно интересная идея. Например, для таких пользователей можно заранее более жёстко какие-то другие меры отрабатывать, типа влогинивания с незнакомого компьютера.
Ну или просто после трёх-пяти попыток войти с неверным паролем не капчу показывать, а хост блокировать — тут уж как у кого фантазии хватит.
Просто гениальная идея, особенно в сочетании с солью. Вы бы сразу злоумышленнику README написали: «Если наша база была уркадена вами, то настоятельно рекомендуем начинать взлом с этих паролей — они короче и содержат меньше спец символов».
а еще можно отдельным полем сохранять использованный алфавит…
Злоумышленник все равно найдет, какие пароли — слабые.
А если сервер такой нехороший, что не принимает длинный пароль при регистрации? А ведь ему должно быть без разницы — на сервере будет храниться хешированная сумма.
Я вас понял, тут вопрос только в валидации данных. Конечно ограничение должно быть, но в пределах нормы. В статье как раз по этому поводу есть 2 пункта:
Не забываем проверять переменные на граничные значения.

Не позволяем загружать длинные строки и тяжелые файлы.
>> авторизация по ключам
А как потом при увольнении сотрудника пройтись по всем серверам и стереть его ключи?
удалить ключ с сервера не сложнее смены пароля. Разве нет?
Не сложнее. Но топик категорично говорит используйте ключи, а не скажем LDAP. При LDAP я могу отключить авторизацию по ключам, а уволившихся лочить и всё само работает.
К сожалению, LDAP нарушает еще одно правило безопасности: использовать для разных серверов разные пароли. Вот если бы можно было хранить в LDAP ключи… Или соединить ssh и kerberos…
О, отлично… Значит, так и надо делать.
А чем единый ключ из LDAP отличается от одинаковых паролей?
Ключ имеет публичную и приватную части. Это не даст серверу, на котором происходит регистрация, выступить от имени пользователя на другом сервере.

Строго говоря, разделение ключей — еще не гарантия невозможности MITM-атаки через чужой сервер в общем случае, но ssh при использовании авторизации по ключам от такой атаки защищен.
Статья отличная, спасибо! Но вы ничего не сказали об устаревших и небезопасных функциях хеширования… Например в PHP это md5() и sha256().
Да, про это действительно забыл, спасибо
желательно проверять на криптостойкость и коллизии.

Поделитесь, какими методами вы это делаете?
делать проверку по радужным таблицам по популярным паролям, если есть совпадение — пароль не принимать, ну и опять же не давать юзеру создавать слишком простые пароли
То есть нужно:
1) где-то найти адекватную радужную таблицу
2) залить ее к себе на сервер
3) перед регистрацией проверять вхождение вводимого пароля (наверное все же его хеша?) в эту таблицу

То есть, если это таблица в БД — имеем дополнительный запрос. Если это файл — имеем поиск без индекса.

Как-то так?
В нашем случае нам нужна RT, основанная на словарях популярных паролей, а далее при регистрации проверять попадание хэша.
Но эт конечно если у нас простая соль. Если мы будем использовать уникальную соль для каждого пароля — то радужные таблицы становятся не эффективными.
Что такого делает ваш сервис, чтобы не давать юзерам делать слабые пароли? Я могу понять, скажем, веб-интерфейс клиент-банка. Но для каждого форума или браузерки — это уж чересчур.
Раз Вы его используете, можете немного вкратце рассказать, что это и с чем его едят?
Чтение этого RFC-подобного мануала как-то не располагает к себе…
Вместо куки серверу в момент аутентификации предлагается возвращать JSON-объект, подписанный при помощи HMAC и секретного ключа сервера. В этом объекте может храниться собственно айдишник пользователя, таймстамп истечения сессии, и любая друга информация которая может быть полезна для работы с пользователем.

Дополнительные параметры могут использоваться как на клиенте (если там стоит isAdmin: true, то показывать админские элементы в интерфейсе), так и в логике на сервере при последующих запросах. При этом на сервер токен стоит передавать при помощи кастомного хедера в запросе — и тогда атаки CSRF перестают работать автоматом.

Дополнительный плюс — сервер может не хранить состояния, а просто доверять данным, которые в токене пришли (если токен не протух и валидация HMAC секретным ключом прошла, естественно). Соответственно, можно использовать внешний auth-сервер для многих проектов. Ну или шардиться по многим серверам без дополнительных усилий.
вообще говоря я использую похожую схему:
— При аутентификации сервер отдает уникальный токен: никакой полезной информации в нем нет. просто уник. хеш(udid или sha1, например)
— Сам его сохраняет, скажем в redis в виде пары {token: user_id}
— Далее при авторизации клиент присылает в заголовке Authorization этот токен…
Где клиент хранит этот токен мне по сути неважно, но обычно это локальный SQLite…
клиент-серверное взаимодействие over https

Так вот, в чем минусы этой простой схемы перед JWT?
Извиняюсь, меня смутил HMAC, показалось, что это какой-то алгоритм шифрования для JSON… А в токене зашифрована вся инфа… Чего только себе не понавыдумываешь, не разобравшись…

Я его похоже и использую (обычный json в котором есть токен) :)
Ну, единственное чем Ваш способ отличается от канонического JWT — это наличием редиса как хранилища сессий.
Это в общем нормальная ситуация, но JWT может обходиться вообще без него, что многое упрощает, перекладывая заботы о state с серверного хранилища на параметры клиентского запроса.
Что плюс, так как таким токеном можно сколько угодно серверов/сервисов аутентифицировать при необходимости.

Ну и да, из JWT можно на клиенте достать ту же инфу про полномочия учётки (claims в терминологии JWT), но вы её тоже скорее всего вместе со своим токеном передаёте при необходимости — так что тут разницы кроме более занудного следования RFC не должно быть.
Удобства не много на самом деле.
Во первых доверять isAdmin подписанной куке это плохая концепция. Вероятность того что секретный ключ утечет (session_secret в рельсах) выше чем кажется. Истечение сессии это хорошо, но в куках есть такой же механизм. Ну и наконец CSRF тут палка с двумя концами — либо вручную слать хедер каждый раз либо слать csrf_token для валидации запроса, эквивалетнтно.
Речь шла о использовании полей вида isAdmin на клиенте, насколько я понял. Это безопасно.
Естественно клиент не знает секретного ключа. (Виноват, не сказал этого достаточно ясно.)
Он просто доверяется тому, что находится в открытых для него полях, а сервер всё равно проверяет подпись ключом, который только у него есть. И проверяет полномочия конкретного аккаунта получать/изменять запрашиваемую информацию.

CSRF-токен в классическом понимании, одноразов и пробивается по базе (не обязательно, но так почему-то любят делать).
Но в общем-то Вы правы, JWT-токен — просто несколько расширенный CSRF-токен, простоself-contained, и никакой внешней информации ему для валидации в общем случае не надо. Что делает сервер лишённым состояния — а это плюс.
Так, я по-моему не совсем верно Вас понял.
Вы не могли бы пояснить свой тезис про утекание секретного ключа с сервера?
Я немного копал тему хранения данных на клиенте. И у меня сложилось такое мнение:

Дополнительный плюс — сервер может не хранить состояния, а просто доверять данным, которые в токене пришли (если токен не протух и валидация HMAC секретным ключом прошла, естественно). Соответственно, можно использовать внешний auth-сервер для многих проектов. Ну или шардиться по многим серверам без дополнительных усилий.

Это очень заманчиво, но

Вместо куки серверу в момент аутентификации предлагается возвращать JSON-объект

Куки есть «из коробки». Это, возможно, не самый удобный механизм, но он есть, работает везде и проверен временем.
JWT — все-таки экзотика.

подписанный при помощи HMAC и секретного ключа сервера. В этом объекте может храниться собственно айдишник пользователя, таймстамп истечения сессии

Вот тут эта манящая техника ставит нам подножку — мы не можем закрыть сессию раньше, чем истечет ее TTL (тот самый таймстамп).
Клиент может отправлять старый токен пока он валиден. Другими словами — Replay attack.
Чтобы избежать этого нужно что-то хранить на сервере и тогда — прощай плюшки из первой цитаты.

Плюс нужен механизм «плавной» смены ключей для HMAC.

Итого:

Сессии (кука + данные на сервере):
+ простейший надежный механизм
— масштабирование

Токен/Куки (все на клиенте):
+ масштабирование
— сложный кастомный механизм
— replay attack
— смена ключей

Приглашаю к обсуждению вас и Chikey
Добавлю: сервис, получивший JWT, может использовать его для авторизации на другом сервисе. Это может быть удобно в многоуровневых архитектурах — но только до тех пор, пока все сервисы принадлежат одной организации.
Добавлю плюсик к JWT:

Однажды на одном довольно сложном проекте клиент попросил возможность легко переключаться между несколькими пользователями… Тогда я и решил для себя, что механизм cookie-based сессий мне вообще не нравится :) А HMAC позволяет хранить их на клиенте в любом количестве и ротировать, как душе угодно…
Хотя, конечно, ничто не мешает добавлять куки после аутентификации в отдельный массив, и переиспользовать их, когда нужно…
Ну, для этой задачи не обязательно выносить сессию полностью на клиент, как сделано в JWT. Достаточно сменить способ передачи session id на более управляемый со стороны javascript.

Мы в своем проекте для той же цели используем пользовательские заголовки HTTP. При отсутствии заголовка — берем session-id из куки. В итоге есть одна «основная» сессия, которая возникает при переходах между страницами — и несколько «дополнительных», доступных только при ajax-запросах.
Ну, JWT-токен может точно так же иметь уникальный id, указывающий на сессию, по которому статус этого конкретного соединения можно поднять из какого-нибудь редиса.
То есть с этой точки зрения подписанные куки и JWT ничем не отличаются вообще.

Сложный кастомный механизм — это достаточно спорно, по крайней мере `JWT.decode` и `JWT.encode` есть, похоже, для любого языка вообще.
Мне кажется правильным выносить JWT-авторизацию в какую-то прослойку моего фреймворка (типа Rack middleware), который берёт на себя валидацию токена из заголовка запроса и выдаёт отлуп при несовпадении. Там же и какую-то key derivation function можно разместить при необходимости, которая берёт скажем общий секрет и текущую дату, и из них делает актуальный ключ. (И, соответственно, проверяет токен несколькими секретами за последние пару дней, последовательно. Выбор конкретной KDF обосновать сейчас никак не могу, не задумывался над этим.)

Это не очень много кода, достаточно прозрачного. Надо будет гем сварганить как-нибудь на досуге.

PS. Сам предвкушаю продолжение дискуссии со стороны многомудрого Chikey
1 как защитить от слития токена через XSS? Он же доступен в жс
2 как отослать его при первом запросе на сервер? Его можно держать в локал сторадже и только потом прикреплять — растет общее число запросов.
Ну, если у атакующего есть XSS, то он в любом случае может отправить любой запрос к нашему серверу от имени угнанного клиента. Этот вариант поэтому исключим из рассмотрения.

В итоге, единственное что сможет сделать атакующий с токеном и не сможет с http-only кукой — это отправить его к себе на сервер.
Соответственно, он получит:
1) возможность прочитать, что там за id и прочие claims в неё зашито. Это он и так бы смог через XSS узнать в любом случае, выцарапывая данные из оттуда, где там они в модели есть.
2) возможность проводить оффлайн-атаку. В подбор серверного секрета я не особо тут верю, он может и должен быть достаточно длинным. SHA256 на одном современном процессоре даёт что-то типа 100 мегабайт в секунду — можно прикинуть количество лет, которое все процессоры земли будут подбирать единственную коллизию «в лоб».
Для более слабых хешей (в частности MD4) существует осуществимая атака по перебору прообразов под заданную подпись — в случае SHA256 она вроде как тоже нереальна, но на всякий пожарный в JWT предусмотрена возможность перетыкать шифроалгоритмы.

Я что-то упустил?
3) Возможность угнать сессию и продолжать действовать от имени пользователя даже после нажатия на кнопку «выход» (в случае полностью клиентских токенов).
Да, любой чистый stateless-механизм этому подвержен.
В итоге добавляем айди сессии с привязкой к данным в памяти сервера. Теряем в масштабировании, но сохраняем плюсы «изкоробочной» защиты от CSRF, дружелюбность к произвольным клиентским API и возможность иметь авторизацию «одну-на-всех» и на отдельном хосте.

(Если что — JWT ни в коем раз не «серебряная пуля» для авторизации. И я её «адвокатирую» в первую очередь из интереса попробовать технологию на прочность, постучав её об вдумчивых и проницательных критиков.)
не совсем согласен с первым предложением…
JWT может быть подписан идентификатором клиента(хеш от комбинации IP c userAgent, например), которому он выдан… правда тогда сессия сломается при смене сети(перешел к другому WiFi, например), но использовать этот токен злоумышленнику не получится, не пройдет валидации…
Всё так.
Даже больше того, нет необходимости домешивать id клиента в секрет для подписи, достаточно просто иметь (и проверять при запросах) поля ip: или useragent: в токене.

Другое дело, что лично я считаю привязку авторизации к IP-адресу очень неудобным механизмом, чисто из собственного опыта постоянной работы из всяких удалённых мест.
Про общее число запросов — замечание валидное, но применимое в первую очередь для серверно-наполняющихся веб-страниц.

Мобильные приложения с API через JWT дружатся гораздо проще, чем с куками. Никаких лишних запросов в этом случае нет, потому что авторизацию так и так проверять, и обновлять если сессия сдохла.
Ну и многие веб-приложения можно свести к вязанке статики и получению данных по тому же API, что и для мобильных аппов (и мы пару таких приложений делаем в данный момент, например). Тут тот же случай, что и с мобильными приложениями — сам код приложения, шаблоны-картинки-несвежие данные скорее всего уже на клиенте, в кеше, а с сервера нужны только данные, которые подгружаются асинхронно.
И опять-таки клиент отдаёт на сервер то, что знает про сессию, в первом же запросе. И если она протухла — получает отлуп и просьбу перевойти. А поскольку в токене есть ещё и дата протухания, в отличие от сессионной куки — по идее можно ещё и заранее протухание предвидеть в большинстве случаев, и не делать лишнего запроса с отлупом. (Я так никогда не делал, впрочем. Наверное, стоит попробовать.)
Да, токены для API иногда выглядят проще, чем куки. Но тут надо соблюдать осторожность — чтобы случайно не использовать и токены, и куки, как это сделали в API Сетевой школы и Сетевого города :)
Ну, JWT-токен может точно так же иметь уникальный id, указывающий на сессию, по которому статус этого конкретного соединения можно поднять из какого-нибудь редиса.

Тогда у нас получатся сессии, только задом наперед: у сессий данные на сервере, а id — у клиента, а у нас будут данные — на клиенте, а id — на сервере.

Т.е. мы вернулись к тому от чего уходили — хранение данных на сервере. Плюс по пути создали себе проблем.

Механизм сессий:
— для обмена используются куки
— генерируем рандомный id
— данные в бд связываем с этим id

Это везде работает «из коробки». Механизм прост и даже если вы делаете свой обработчик, то напортачить практически негде.
Плюс куки умеют httpOnly, secure и т.д

JWT:
— для обмена используем кастомный механизм
— шифрование/подпись
— ротация ключей шифрования/подписи
— генерируем рандомный id
— id в бд связываем с данными

В первых трех пунктах легко можно станцевать на граблях. Криптография — это сложно. Если тут будут ошибки — концепт можно смело закапывать. Если представить, что мы используем все готовое, т.е. первые три пункта мы имеем «из коробки», то, все равно, JWT выходит тупо сложнее и запутаннее.
И никаких преимуществ — мы же храним id на сервере. А если его не хранить, то здравствуй replay attack.

Тоже самое касается подписанных/шифрованных кук.

ИМХО, если хранить хоть что-то на сервере — затея не несет никаких преимуществ. А если все хранить на клиенте, то больше всего меня напрягает replay attack и слепое доверие клиенту (пусть даже данные подписаны). Такое себе vulnerable by design.

Что скажете?
Ну и да.
Немного погуглив JWT+нужный язык можно легко найти либы для более-менее всего чего угодно, по моему опыту.
2.3 Используем escaping для любых данных. Проверяем на xss, никаких html тегов или js скриптов от клиента.

Ага, а на хабре вы пост bb-кодами оформляли? :)
Тут конечно погорячился, подразумевалась защита от XSS. Безопасные теги можно разрешать
Да и «эскейпить». Экскейпят — в базах данных. В случае разговора про XSS атаки — приводят сущности, не надо ничего эскейпить.
согласен, тут проблема у меня возникла проблема с терминологией, не правильно выразился
Для файлов по возможности проверяем MIME-тип, не доверяем расширениям, это легко изменить.
Если кто-то захочет что-то подделать, то MIME-тип подделать не сильно сложнее расширения. Если ваш сервер принимает от пользователя картинки — пересохраняйте их.
Тут тоже не все так просто. Да, mime-type подделывается (на клиентской стороне просто — подменой HTTP запроса, на серверной — заголовки). Но и просто пересохранить картинку может быть недостаточно. Можно создать картинку, которая потерпит ресайз, но нужные данные внутри (например — PHP код) останутся на месте. Т.е. заливать файлы вообще желательно на отдельный домен и сервер, где веб-сервер не имеет интерпретаторов.
Нет картинок, которые перетерпят ресайз и не потеряют вложенных лишних данных. Скорее всего вы хреново делаете ресайз, удалите мета-данные. Это раз. Два: не обязательно класть файлы на другой сервер, чтобы их нельзя было выполнить, достаточно дать им название, которое интерпретатор не будет обрабатывать. Думаю, ваш веб-сервер не настроен на передачу php-интерпретатору файлов с расширением png?
Конечно настроен, а что, не надо было?
Или даже так:
— А кто же тогда картинки пользователю отдавать будет?
Нет картинок, которые перетерпят ресайз и не потеряют вложенных лишних данных.
Ошибаетесь, и вот почему. Даже используя функции imagecopyresized() и imagecopyresampled() при уменьшении размера PNG картинки с 256px до 32px шелл остаётся.

не обязательно класть файлы на другой сервер, чтобы их нельзя было выполнить, достаточно дать им название, которое интерпретатор не будет обрабатывать.
Опять заблуждаетесь. Файлы могут быть выполнены с помощью банального LFI и никакие названия файлов не помогут в таких случаев.

Как мы знаем, 100% безопасности не существует (и скорее всего этого никогда не будет), но нужно стараться из-за всех сил уменьшить риск эксплуатации даже неизвестных уязвимостей. И в этом случае автор прав — если действительно хочется защитить сервер, пользовательский контент лучше всего сохранить на другом сервере, ибо такая мера предосторожности очень проста и эффективна.
Спасибо, про картинки инфа интересная.

На счёт LFI: мне кажется, что писать на PHP с динамическими инклудами или евалами — это не то что стрелять себе в ногу, это стрелять себе сразу в голову.

но нужно стараться из-за всех сил уменьшить риск эксплуатации даже неизвестных уязвимостей.
Мне кажется (№2), что в таком случае стоит отказаться от PHP в первую очередь, а хранить ресурсы пользователя на другом сервере во вторую.
Очень рад, что ссылка оказалось полезной. Но позвольте ещё раз не согласится с Вами.

Во-первых, LFI не такая уж простая уязвимость как многим кажется, плюс, почти в любом PHP-проекте файлы инклудятся динамически (например: классы, модули, файлы конфигурации, шаблоны). Вопрос лишь в том, позаботился ли разработчик о безопасности приложения или нет?

Во-вторых, в большинство случаев безопасность приложения зависит только от разработчика, причём не важно какой язык программирования используется (да, у PHP своя репутация по этому вопросу, но ведь это не означает что на PHP невозможно создать безопасный продукт). Для примера, можно взять недавняя XXE уязвимость которая позволяла получить доступ к серверу Google и которая ещё раз доказывает, что критические уязвимости обнаруживаются не только в приложениях разработанные на PHP.
Всегда есть уязвимости. Но сделать их на нетипизированном языке с местами не самым предсказуемым поведением проще.
> Два: не обязательно класть файлы на другой сервер, чтобы их нельзя было выполнить, достаточно дать им название, которое интерпретатор не будет обрабатывать.
ru.wikipedia.org/wiki/PHP-%D0%B8%D0%BD%D1%8A%D0%B5%D0%BA%D1%86%D0%B8%D1%8F
> Нет картинок, которые перетерпят ресайз и не потеряют вложенных лишних данных.
Неоднократно слышал, что это возможно. Хотя, конечно, это от многого может зависеть.

PS. Сори не увидел, что ответ уже есть.
Проверять заголовок Host, не отдавать сайт, если в нём что-то неожиданное (другой hostname). Отдавать в таких случаях, например, HTTP 444 или HTTP 302 (и нормальный адрес сайта в Location).

Добавлять заголовок X-Frame-Options.

Проверять crossdomain.xml в корне сайта.

Никогда не добавлять ссылки с target="_blank", ведущие на сторонний сайт (если это необходимо — указывайте в href ссылки внутренний адрес, с которого будет происходить перенаправление на нужную страницу стороннего сайта).
А чем так плох target="_blank"?
Помимо того, что это раздражает пользователей, вы предоставляете стороннему сайту доступ к вашей странице через объект window.opener. На эту тему в начале прошлого года ещё здесь писали.

В частности, вы можете поменять адрес страницы через window.opener.location. Прекрасная возможность для фишинга. Кстати, работает в том числе и с Google (у него тоже такие ссылки).
Что мне мешает отредактировать DOM через отладчик и дописать свой target? А лучше просто shift-click. Вообще открывать ссылки на другие хосты в новом окне — хороший тон.
Хороший тон — это не блокировать среднюю кнопку мыши, или другие зависящие от браузера способы открытия ссылок в новом окне. Мне нравится, когда я сам выбираю, где мне открыть ссылку.
Вам — ничего не мешает. А пользователю, которого хочет перенаправить сайт, на который вы ссылаетесь, мешает отсутствие понимания того, как это сделать (потому что если бы оно присутствовало, то он был бы не из тех, кого вообще можно обмануть таким образом).

Итак, описываю суть ещё раз.

У вас на сайте ссылка с target="_blank". Ссылка ведёт на сайт Иосифа Александровича. Пользователь щёлкает на ссылку, открывается новая вкладка. В то же время, в новой вкладке загружается сайт Иосифа Александровича, на котором запускается JS-код, который меняет window.opener.location. В результате в той вкладке, в которой был ваш сайт, теперь что-то совсем другое — может, например, страница, которая выглядит точно так же. Пользователь, теоретически, может это и не заметить, потому что он не привык, что открываемый сайт может менять страницу в той вкладке, из которой этот сайт открыли.
Не даем безгранично добавлять какие-либо данные (например, комментарии).

Это еще почему?

Не позволяем загружать длинные строки и тяжелые файлы.

Наверное, вы хотели сказать — не даем загружать больше чем требуется по ТЗ?

Оптимизируем запросы к базе — никаких select в цикле.
Не забываем про индексы.
Сложный поиск? Используйте поисковые движки (ElasticSearch, Sphinx и т.д.).

Это к оптимизации наверное?

В отправке формы или изменений состояний используем уникальные для каждого пользователя токены (csrf). Не хотите токенов, тогда проверяйте HTTP_REFERER.

Странное предложение о реферере после множества слов о том, что любые входные данные подделываются?

Никаких данных на клиенте, даже в зашифрованном виде, только id-сессии в куках.

Уточню, никаких серверных данных, например, хранить громкость звука в проигрывателе на клиенте нормально.
Да и сессионные данные. Возможно, человек никогда не работал с RoR, где используются подписанные куки.
Или с Django, где это тоже есть (и, как вариант, можно даже организовать зашифрованные cookies).

А вообще на современных сайтах куча клиентской информации (ручное перезаписывание которой не приведёт ни к каким негативным последствиям) хранится, например, в local storage в браузере. По-моему, совершенно прекрасная возможность — если какие-то клиентские данные не нужны на сервере, так пускай локально и хранятся. И, опять же, загрузку так можно ускорить, записав туда что-то, что иначе нужно было бы каждый раз запрашивать по сети.
Странное предложение о реферере после множества слов о том, что любые входные данные подделываются?
Так это же в рамках защиты от от csrf. Злоумышленнику реферер в чужом браузере подделать затруднительно.
Подождите, злоумышленник на чьем сайте находится в браузере клиента?
Если на своем, то уже есть встроенная cross-domain защита, если на вашем, то и referer ваш?
правильно проверить реферер тяжелее чем правильно проверить токен. Ну и пустой реферер отослать легко
Мне вообще кажется, что отказывать пользователю в возможности использовать ваш сайт только из-за того, что его браузер настроен на то, чтобы не отсылать referer (или отсылать определённый) — не слишком корректно.

И да, проверка referer — вообще так себе защита от CSRF, скажем прямо.
> Статика (js, css, image) должна лежать отдельно, в идеале ─ на другом сервере.
А почему? Что даст в данном случае отдельный сервер? Чем рискуем в противном случае?
Объясните.
Отдельный сервер — не дает ничего. А лежать отдельно она должна — потому что так на самом деле сервер настроить гораздо проще…
если ваш сайт хранит сотни гигобайт изображений, то вопрос свободного места на диске стоит очень остро. В таких случаях все изображения выносятся на отдельный сервер, где и хранятся. Так например устроен habrastorage. Да и сам сервер разгружаем, ему не надо отдавать лишние мегабайты статики.
Это оптимизация, с ней понятно. Имелось в виду в контексте безопасности.
Можно рассматривать эту оптимизацию в рамках защиты от ddos. Как было уже сказано это ближе к оптимизации, чем к безопасности.
Тут скорее ближе к оптимизации. Статику можно вынести на nginx (который её очень славно умеет готовить) и чаще всего не надо шифровать. То есть статику отдавать с помощью простого HTTP, а все остальное по HTTPS.

Серверу (серверам) жить будет легче.
И будет у вас браузер ругаться, что часть трафика незашифрована.
И при MitM легко подменить контент на отдаваемой странице, если хоть один из скриптов передавался по http.
В добавок ко всему вышесказанному (тоже из разряда оптимизации) — при статике на отдельном сервере, нет необходимости посылать все куки с основного сервера (значит заголовки запроса меньше, трафик меньше). Кроме того, если отрубить скрипты на статик-сервере, даже в случае дырки в аплоадере, файл злоумышленника ничего не сделает :)
Что даст в данном случае отдельный сервер? Чем рискуем в противном случае?
В случае отдельного сервера можно достаточно спокойно предоставлять доступ к нему. Дизайнеру для правки шаблонов, пользователям для заливки своих файлов и так далее.
Изолировать весь этот «входной/сторонний» бардак на отдельном сервере где скрипты в принципе не будут исполняться — очень даже надежное решение.

На free-lance.ru кстати был косяк, которого они могли бы избежать. У них в результате неправильной настройки сервера статики — сервер стал отдавать (при определенных запросах) в «чистом» виде все файлы, даже предполагавшиеся быть исполняемыми или закрытыми.
А поскольку и статика и конфиги и скрипты лежали на одном сервере в соседних папках, то нехило там у них всего утекло (г-но код кстати редкостный был:) ).
Ну, не все так чудесно и радужно с отдельным сервером. Если он не на другом домене — то заливка статического html с вредоносным скриптом позволит обойти защиту от csrf, так что доступ к нему все надо предоставлять столь же аккуратно, как и к серверу со скриптами.
Выше по треду была приведена ссылка на веб-шелл, зашитый в картинку png.
И да, когда люди говорят про отдельный сервер под статику, они наверняка имеют в виду и отдельный домен тоже (static.example.com, например).
Простенькая защита от LFI.
Не хотите токенов, тогда проверяйте HTTP_REFERER.

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

Я бы сказал вариант вообще не рабочий.
UFO just landed and posted this here
Ну, вот еще, к примеру. В любом случае полагаться на секьюрность ПО установленного на клиенте не очень хорошая идея.
Кроме того, этот заголовок не является обязательным, вроде бы хром вообще можно запустить с ключом --no-referrers, существуют также расширения позволяющие отключить/управлять им.
>Никаких левых папок и файлов а-ля .svn, .git, .idea, dump.tar.gz в корне проекта не должно быть.
Иногда они нужны.
Тогда надо делать так:

location .svn{
 deny all;
}


Ну а за .idea, dump.tar.gz — надо того кто закоммитил эти файлы бить по рукам научить пользоваться ignore-файлом нужной SCM (например, .gitignore).

при переносе сайта с nginx на apache про конфиг могут забыть, а htaccess не завести. Так что надо быть очень осторожным.
Согласен. Кстати, встречал проекты где .htpasswd лежал аккурат рядом с .htaccess
Да вы видимо шутите? Половина из написанного бред, четверть не относится к сек юркости века, еще четверть даже не описана как и почему должна использоваться. Не статья, а детский сад.
В том-то и дело, что с безопасностью и причёсыванием серверов творится чаще всего как раз детский сад. Советы очевидные, но нужные. Хотя объяснений, возможно, и правда не хватает.
3. Используем всегда актуальные версии софта, вовремя обновляемся.

История с OpenSSL как раз-таки показала, что не всегда стоит бросаться на самую свежую версию, если обновления не содержат правок критических багов/уязвимостей. Heartbleed появился начиная с версии 1.0.1, а те кто остался на 9.8.* — 1.0.0 не были подвержены уязвимости.
И одновременно она же показала, что когда эти самые правки критических багов выходят — обновляться надо как можно скорее.
>Для файлов по возможности проверяем MIME-тип, не доверяем расширениям, это легко изменить.

Как раз наоборот habrahabr.ru/post/211973/
Второй комментарий к той самой статье: habrahabr.ru/post/211973/#comment_7292335

Доверие MIME-типу не означает, что можно игнорировать проверку целостности принятых данных (соответствие MIME-типа расширению) или проверку допустимости этого самого типа (не принимать html вместо картинки).
Используем всегда актуальные версии софта, вовремя обновляемся.
Недавняя история с OpenSSL тому подтверждение


Палка о двух концах. Эта же история есть подтверждение тому, что новая и актуальная версия имела этот баг, а старая — нет. Ребята, сидящие на FreeBSD 9 и ниже с OpenSSL не из портов, например, не подверглись этой проблеме.
Кстати, лично проверял два сервера в прошлом месяце — оба оказались в безопасности как раз из-за того, что на одном была версия 1.0.0j-fips 10 May 2012, а на другом вообще 0.9.8k 25 Mar 2009. Ни ту, ни другую версию OpenSSL уязвимость CVE-2014-0160 не затрагивает.
Очень базовый мануал.
Из довольно эффективного хочу добавить:
1) Куки недоступные из JS, если нет такой необходимости.
2) X-Content-Security-Policy и другие заголовки.
Когда в своем приложении вы подписываете данные, часто это бывает так:

md5(message + code), вы можете быть подвержены Length Extension Attack:

blog.whitehatsec.com/hash-length-extension-attacks/
en.wikipedia.org/wiki/Length_extension_attack

Пару лет назад flickr был подвержен именно такой атаке — netifera.com/research/flickr_api_signature_forgery.pdf

sha1 и другие простые функции тоже подвержены этой атаке. Чтобы их обойти, нужно использовать подпись сообщений по HMAC.

P.S тут речь идет не о хэшировании пароля
Если подписано именно так: md5(message+code) то как раз атака не пройдет.
Пройдет если md5(code+message).
Заставляем клиента делать сложные пароли.

Нет, ну вот всегда искренне недоумевал, зачем меня заставляют вводить заглавные буквы и цифры? Не рекомендуют, а именно заставляют?
Я использую пароли, в т.ч. и для доступа к деньгам, которые выглядят как-то так csdl,mqb.
И вот скажите, почему они считается «простыми»?
На случай если сольют всю базу — уменьшить шансы успешной расшифровки пароля пользователя.
Также для предотвращения успешных брутфорс атак.
Такого же эффекта можно достичь, заставляя пользователей вводить более длинные пароли.
8-символьный пароль из английских букв нижнего регистра имеет тот же порядок сложности, что и 6-символьный пароль с верхним + нижним регистром и цифрами: 26^8 ~~ (26*2+10)^6.
Вообще, заставлять (именно заставлять, а не рекомендовать) пользователю следовать жестким правилам — плохо. Очень плохо. Если для пользователя ваш сервис не является крайне необходимым — вы рискуете его потерять.
Гораздо лучше при попытке ввести слабый пароль устроить пользователю ликбез, о том, какие пароли плохие и почему. А там уж пусть сам решает, как писали выше, рисковать ли своими данными.
К тому же, любая эвристика определения слабых паролей не идеальна. Я много раз сталкивался с тем, что пароль вида «Vasya123» считается надежным, а пароль «csdl,mqb» — не проходит. Хотя попробуйте его забрутфорсить!

Ага. Добавлю по поводу эвристик.
Обычно я пишу короткую фразу русскими буквами, но в английской раскладке — если мне не интересна безопасность учетной записи (скажем, надо зарегистрироваться на форуме, чтобы увидеть картинки в интересующей меня теме — кстати, какой идиот придумал так делать?).

Какого же было мое удивление, когда я забыл переключить раскладку — и пароль вида фыварулит был признан сильным, поскольку якобы имел в два раза большую энтропию, чем asdfhekbn
По ssh: можно использовать технику port knocking для открытия необходимого порта [+ ограничение по ip] [+ ограничение по mac-адресу].
Что mac, что ip подделываются, кэши/таблицы коммутаторов и маршрутизаторов отравляются и т. п.

Смысл ограничения по ip может быть только при статичной системе. Правда можно забыть про это ограничение и при изменении ip управляющего хоста получить кусок радости.

Какая либо фильтрация по mac разумна для l2/l3 устройств. При чуть более высокоуровневом ssh оно выглядит странно. Поменяется, например, роутер около хоста (маршрут через другой роутер пройдет) и всё.

Port knocking сильно усложняет жизнь при необходимости получить доступ с другой рабочей станции/телефона/планшета. Это вопрос личного геморроя.
Sign up to leave a comment.

Articles