??? ???? ?????? ????????

Для защиты от CSRF вы должны использовать анти-CSRF токены и только их. © pyrk2142
Типичные ошибки при защите сайтов от CSRF-атак


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

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

Что мы подразумеваем под csrf атакой?
Вольный пересказ вики:
CSRF (англ. Сross Site Request Forgery — «Межсайтовая подделка запроса», также известен как XSRF) — вид атак на посетителей веб-сайтов. Жертва, авторизованная на сайте А(target), заходит на сайт В(зловред), в ходе просмотра которого код, внедренный в страницу сайта В отправляет запрос к сайту А. Поскольку пользователь авторизован на сайте А, при отправке запроса передаются куки, таким образом сайт А предполагает что запрос порядочный и выполняет его. Сайт А в этой ситуации признается уязвимым к csrf атаке.

Вводная:
  • javascript в броузере пользователя включен
  • броузер отправляет/принимает (и обрабатывает) http headers согласно спецификациям (не пригодилось)
  • уязвимости броузеров не принимаем во внимание
  • отклонения от стандартов в поведении броузеров принимаем во внимание
  • сайт А — работает только по https, перехват трафика не рассматривается


Как предлагается защищаться от атаки в интернетах?
Генерируем токен (мандат), прикручиваем его к форме (скрытым полем) и при отправке запроса на сервер передаем в том числе и его.
На сервере проверяем валидность токена (и возможно на какой вид операции выдан) и если все в порядке — выполняем запрос, иначе с негодованием отвергаем.
Как это работает? Когда зловред формирует запрос к сайту A(через страницу сайта В, открытую в броузере авторизованного на сайте А клиента), броузер автоматически передает при запросе куку (пользователь то авторизован). Соответственно помимо куки должен быть секрет, не передающийся автоматически серверу (то есть не хранящийся в куках) и недоступный зловреду. Это и есть токен.

То есть токен служит гарантией того, что запрос сформирован контролируемым нами кодом. Есть ли другие способы понять, какой код сформировал запрос? Да, есть один, и он до безобразия прост.
HTTP Referer
Проверяя реферер, мы гарантированно отсеиваем запросы, сформированные не с наших страниц. Так ли это?
В интернетах полно утверждений что этот способ ненадежен, так как реферер легко подделать. Это, мягко говоря, не так. Давайте не будем выпадать из контекста — зловреду надо подделать запрос из броузера, в котором сидит авторизованный на нашем сайте клиент. Так вот, этот реферер зловред подделать не может. Либо уйдет реферером url зловредного сайта либо пустой реферер. Если мы на своей стороне (на сервере) делаем проверку сессии (куки) и реферера (на принадлежность нашему домену) отсеивая пустые и левые рефереры — то злоумышленик не сможет просочиться.
Именно обработка пустых рефереров влияет на надежность схемы: отбрасываем — схема надежная, принимаем — подставляем клиента. Именно многочисленные реализации второго сценария сформировали мнение о слабости схемы.
А как же быть с подделкой реферера? Она есть и это факт. Но зловред может подделать реферер только со своего компа (подконтрольного), на котором не будет сессии (куки). Таким образом злоумышленник не может собрать в одном месте поддельный реферер и куку сессии для осуществления поддельного запроса. Схема надежная, токены не нужны. Это одна из двух схем, которые я пока что выбрал для себя. Итого:

Решение #1
Смотрим реферер, если пустой или не наш домен — шлем в лес. Для проектов без cross domain requests, небольшая часть пользователей (пара процентов) испытает неудобства. Можно разрешить CORS и вести wite list доверенных доменов.

Но эта схема не подойдет для проектов, принимающих запросы с неограниченного числа сайтов (cross domain ajax, например кнопка like у facebook и тому подобное).

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

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

Ок. То есть реферер роли не играет (мы хотим обслуживать клиентов без реферера). В этом случае токен нужен. Но! Есть нюансы.
Первое, второе и десерт — на каком этапе появляется токен, как долго живет и где хранится?
Множество ресурсов рекомендует создавать токен при авторизации и использовать его на время сессии. То есть токен, получается, брат-близнец куки, просто хранится в другом месте. Нормальное такое решение. Все меняется когда приходят они. Нюансы.
Как долго живет сессия? Если до момента закрытия страницы, то все нормально. Закрыли страницу, сессия померла, при повторном открытии требуется авторизация, после прохождения которой будет выдан токен.
Что если мы хотим запомнить пользователя надолго? При закрытии страницы кука остается, при повторном открытии страницы пользователь авторизован, все довольны. Вопрос — откуда взять токен? Этот же вопрос возникает при переходе на другую страницу авторизованным пользователем. Отдать токен в теле страницы при открытии? Тогда мы откатываемся к началу, так как — если мы отдаем верстку страницы с токеном при запросе авторизованного пользователя, то мы отдадим токен зловреду, который запросит ее через авторизованного пользователя, отпарсит страницу, вытащит токен и сформирует злозапрос. То есть в этой ситуации вместо одного запроса для совершения злодеяния потребуется два. Не более того. Но! Это праведливо если мы разрешили cross domain requests через CORS. Если CORS не нужен, то все в порядке. То есть:

Решение #2
На реферер не смотрим, генерим токен при авторизации, прибиваем гвоздями к верстке, время жизни куки — до закрытия страницы. Cross domain requests, CORS, (см. ниже) — влияния на безопасность не оказывает. Годится для одностраничных web-приложений. При перезагрузке страницы необходима повторная авторизация (либо см. решение #3)

Решение #3
На реферер не смотрим, генерим токен при авторизации, прибиваем гвоздями к верстке, время жизни куки — любое. Cross domain requests, CORS, (см. ниже) — запрещены. При загрузке страницы авторизованным клиентом токен повторно прибивается к верстке. Годится для многостраничников.

Тут хочу отметить деталь в работе CORS. Атака зловреда сорвется именно из-за токена а не из-за запрещенного CORS. Первый запрос на чтение страницы с мандатом пройдет до сервера и ВЫПОЛНИТСЯ им (если мы не обрабатываем хидер origin до обработки запроса), ответ вернется клиенту, но не отдастся злоскрипту, таким образом тот не сможет выделить токен. Если мы работаем без токена, то запрет CORS нас никак не спасет — зловред может сформировать запрос, который дойдет до сервера и выполнится им, просто зловреду не видно будет ответа сервера, что не всегда и нужно. Так что хинт: не пренебрегайте вниманием к хидеру origin. В нашем случае это синоним реферера. Казалось бы — а зачем? Да, на безопасность в этом сценарии не влияет, но проверка origin (как и реферер) до обработки позволит отклонять левые запросы без их обработки. Может влиять на производительность ( например от имени авторизованного пользователя зловред может запустить ядреный поиск, недоступный не авторизованным пользователям. Результат не увидит, а вот серваку вашему возможно придется покряхтеть винтами)

Но вот нам в голову приходит светлая мысль улучшить не только наш сайт но и весь интернет и мы начинаем распространять новый модный сервис с кнопочками, лайками и фоловами.
То есть надо разрешить CORS. О, в этот момент все преображается и начинает играть новыми красками!
Как только мы разрешаем CORS, мы теряем возможность хранить токен в верстке страницы (см. выше). Но хранить его где-то надо. Надеюсь, отчаянность мысли хранить токен в куках объяснять не надо.
Путем нехитрых умозаключений приходим к простому пониманию — транспортировка данных от сервера до клиента у нас идет по двум маршрутам — http headers и верстка страницы (либо json ответ, в данном контексте не важно). Все, других путей нет. Хидеры отпадают (да, XMLHTTPRequest дает доступ к хидерам от сервера, но все что можем прочитать мы — прочитает зловред), остается верстка. Но в верстку тоже нельзя, мы уже разобрали тот момент, в котором зловред вытаскивает токен от имени авторизованного пользователя. То есть авторизованному пользователю отдавать токен нельзя. А не авторизованному? Вооот! Единственный момент, когда мы безопасно можем отдать токен — это неавторизованному пользователю в момент авторизации, когда мы получаем от него логин и пароль. До этого момента он для нас никто, после этого момента (после выставления куки) — он для нас зловред.
А где хранить токен? Не в верстке и не в куках. LocalStorage вполне подойдет. Итого:

Решение #4
Генерим токен только при авторизации. Скриптом сразу кладем в localStorage, при каждом action добавляем токен в форму (можно повесить на обработчика submit)
Неограниченный круг лиц и доменов, рефереры не важны, спим спокойно. Годится для одностраничников, многостраничников, при генерации верстки на сервере, на клиенте. В общем серебряная пуля. Ок.
Довольный результатом, я зашел в redmine, оформил все заметками в вики и продолжил работу над проектом. Но мозг не унимался, осталась какая то незавершенность в вопросе.
Перечитав все несколько раз, глаз зацепился за фразу про то что токен — брат-близнец куки, просто хранится в другом месте. Ок. А что если он не брат-близнец, а клон куки? То есть такой момент. Отдавая токен серверу, мы ведь фактически подтверждаем не факт владения секретом (эту функцию выполняет кука) а факт управления процессом, то есть контроль над транспортом. Ведь фактически, если мы не будем генерить токен, а возьмем куку (на клиенте), скриптом вставим ее в передаваемые параметры — что изменится? НИЧЕГО! Зловред не сможет это повторить! Давайте заново. Зловред не может прочитать нашу куку, но может инициировать ее передачу серверу при запросе, передачу в хидере! Это важно. Но он не может прочитать эту куку и передать ее в параметре формы. А мы можем! Итого:

Решение #5
Токены не нужны (в смысле их генерация и валидация) На клиенте — перед каждым запросом читаем куку и добавляем ее в параметр запроса. На сервере — сравниваем куку с параметром запроса, если совпадают — дальнейшие проверки (факт авторизации, права и прочее). Но! В этом случае мы вынуждены отказаться от Http-only кук и открыть доступ на чтение куки javascript-у. XSS-фобам это может не понравится.

Что же, пораскинем. Можно не убирать Http-only, а отдавать сгенерированный session id при авторизации и в куку и в скрипт, который ее сразу положит в localStorage. А дальше — также как и в решении #5. Ок? Нет. Сохранение куки в localStorage равносильно отмене Http-only с точки зрения XSS-атаки.

update. осторожно, криптография
мне тут подсказывают, что куки может бы две, одна доступна для чтения, другая нет. А токен может быть хэшем от session_id. Хорошие мысли!
итого. Две куки, одна доступна для чтения javascript. Мы защищены от XSS, продолжаем защищаться от csrf. Так вот, мысль следующая. На сервере при авторизации генерим некий id, также, как и для сессии. Но не отдаем его, а удваиваем (складываем строку саму с собой) и пропускаем через rc4, со своим паролем, который лежит только на сервере.
Далее — первую половину делаем кукой сессии, вторую — кукой токена.
На клиенте, как и обсуждалось — скриптом подтягиваем куку токена и передаем либо в форме либо в заголовке.
На сервере при проверке — складываем токен и куку сессии, прогоняем через rc4 и сравниваем. Если совпадает — токн верный, проверяем куку сессии на валидность, разворачиваем сессию. Если нет — шлем в лес.
В чем преимущество? Во первых минимальный оверхед. Как я уже указал в комментах, минимум операций на каждый байт, ни в какое сравнение с md5 или sha. Реально применимо для хайлоада. Кроме того, после того, как мы сравнили токен и куку сессии, мы достоверно знаем что клиент авторизован. Мы не знаем кто он, но знаем что авторизован. При этом не обращаясь ни к memcached, ни к sql, не разворачивая сессию. Это очень удобно для действий, доступных всем авторизованным пользователям.

в общем пришел вот к такому коду для node/io.js (черновик):
var key = "пишем сюда наше ключевое слово для генератора (ascii), не более 256 байт";
var offset = "смещение, 256, 512, 1024, для усиления стойкости. значение не влияет на быстродействие";
var i, j, x;
var s = [];
//init s
for(i = 0; i < 256; s[i] = i++);

for(i = 0; i < 256; j = (j + s[i] + key[i % key.length]) % 256, x = s[i], s[i++] = s[j], s[j] = x);
//прогоняем s на offset ходов (равносильно отбрасыванию первых n байт шифртекста для усиления кода
i = 0;
j = 0;

while(offset-- > 0) ++i %= 256, j = (j + s[i]) % 256, x = s[i], s[i] = s[j], s[j] = x);

//фиксим s замыканием
var rc4 = function(s){
    return function (str) {
        var res = '';
        var i = 0;
        var j = 0;
        var x;
        var y = str.length;
        while (y < 0) {
            i = (i + 1) % 256;
            j = (j + s[i]) % 256;
            x = s[i];
            s[i] = s[j];
            s[j] = x;
            res += String.fromCharCode(str[y--] ^ s[(s[i] + s[j]) % 256]); 
            //нам нужен hex, переработать. Нужен ли? вообще то да - нам его еще передавать по http.
        }
        return res;
    }
}(s);
//при авторизации
var cipher = rc4(session_id + session_id);
token = cipher.substr(0, cipher.length/2);
cookie.id = cipher.substr(cipher.length/2);
//выставляем куку, отдаем токен любым удобным способом

//при обработке полученного запроса:
cipher = rc4(token + cookie.id);
token = cipher.substr(0, cipher.length/2);
cookie.id = cipher.substr(cipher.length/2);

if(token !== cookie.id){
    //прекращаем обработку запроса
    //например, кидаем 403 статус
    //или throw exception, в зависимости от структуры сервера
}
//здесь нормально работаем, токен честный
//можно выполнять любые действия, которые разрешены авторизованному пользователю без выяснения его персоны

//либо по session_id поднимаем сессию и выясняем, кто он такой

Спасибо powerman dm9 lucky_libora.
Прошу высказываться.

Не, не вариант. Зловред сделает xor токена и сессии (зарегистрировавшись на ресурсе), получит тем самым xor первой половины со второй половиной от генератора. и последовательно отксорив с токеном и сессией, получит сам генератор, что позволит ему притворяться авторизованным пользователем. А жаль, мысль красивая была.
В общем здесь продолжение мысли.

Прикольно! А почему так получилось (о пятом решении)? Поразмышляв, пришел к выводу что это произошло по той причине, что мы сделали из токена полную копию сессионной куки по функционалу, различие лишь в транспорте. Ок. В пятом решении мы отказались от токена в пользу куки, оставив альтернативный транспорт. А если сделать наоборот — отказаться от куки? Тоже вариант. То есть:

Решение #6
Традиционной сессии нет (через http headers). Id сессии генерится при авторизации, скрипт на клиенте при получении id сразу кладет его в local storage и при каждом запросе к серверу добавляет к запросу. Этого достаточно как для поверки авторизации клиента так и для защиты от csrf. Но отказ от http кук не делает это решение более защищенным от XSS чем пятое решение.

Такие вот мысли.
За бортом остались:
  • обсуждение методов доступа (GET, POST, HEAD, экзотика). Я не использую GET, что бы какой нибудь умник не мог, запостив «картинку» с нужным урлом на посещаемом ресурсе нагрузить мои сервера. В остальном без разницы — все что можем сделать с XMLHTTPRequest мы — может сделать зловред.
  • протоколы доступа. http я для себя вычеркнул, пока https, поглядываю на wss, но пока внятных мыслей нет. Буду рад комментариям по этой теме.
  • Разница в правах на броузере клиента между добропорядочным кодом с нашего сервера, подгруженным в страницу зловреда через script src и изначально чужим кодом, делающим запрос к нашему серверу. Хотя да, надо бы тоже подробно посмотреть.

В общем для себя я выбрал первый и пятый варианты, но пока не переписываю код, жду когда остынет. Предлагаю обсудить.

ссылки по теме:


P.S. Если в чем не прав — не стесняйтесь, бейте по пальцам и учите разуму, я не обидчивый.

Комментарии (58)


  1. MaximChistov
    10.05.2015 18:04

    Схема надежная, токены не нужны

    Потому что вам так захотелось?) Вот, почитайте
    intsystem.org/812/stripping-referer-in-redirect


    1. gonzazoid Автор
      10.05.2015 18:18
      +4

      да, внимательно читал при написании статьи. Там ведь вот какой момент:

      В общем если вас интересует как отредеректить пользователя без указания Referer то прошу под кат

      а я предлагаю всех с пустым реферером посылать в лес (в первом решении). А способов именно подделать (то есть указать произвольный) реферер нет, разве что только на уязвимостях броузеров. Но про уязвимости указано в вводной.


      1. MaximChistov
        10.05.2015 18:24
        -14

        А ничего что некоторые файерволлы/роутеры сами Referer отрезают?)


        1. gonzazoid Автор
          10.05.2015 18:27
          +10

          нет, ничего. Там чуть дальше в статье про это есть.


        1. TimsTims
          11.05.2015 10:46
          +11

          Статью не читай, комментарий пиши


  1. monolithed
    10.05.2015 19:03

    Про CSP забыли


    1. gonzazoid Автор
      10.05.2015 19:53
      -1

      точно, есть такое. сейчас переосмыслю, внесу правки. Спасибо.


  1. powerman
    11.05.2015 08:40
    +8

    Я от использования Referer против CSRF отказался из-за возможности обойти его через "open redirect" — когда на нашем сайте есть url которая возвращает редирект куда скажут (и даже если не совсем куда угодно а только на наш же сайт — в данном случае это не поможет). Такие endpoint часто используются для самых разных нужд (в т.ч. они нужны для безопасности при реализации OAuth2, чтобы скрыть из url полученный код/токен), и даже если при разработке сайта таких нет — где гарантия, что такую «фичу» не добавят через пол года? А если добавят — маловероятно, что при этом сообразят, что эта фича позволит обойти «ленивую» защиту от CSRF и теперь нужно весь сайт переписывать на токены.

    P.S. У Вас в ссылках есть одна на OWASP, и по ссылке с неё можно попать на Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet — там этот момент описан.


    1. gonzazoid Автор
      11.05.2015 15:05

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


      1. dm9
        11.05.2015 16:03
        +1

        Нет, так не работает. Необходимость таких редиректов — часть протокола OpenID (про OAuth не знаю, но, думаю, там всё так же). Другое дело, что слепой редирект кому угодно и куда угодно протокол делать не заставляет. Всегда можно проверить ЭЦП и сделать редирект только если он «законный». Но на практике такую проверку могут опустить: аутентификация не пройдёт, а редирект сработает.


    1. dm9
      11.05.2015 16:19

      Я сейчас проверил ещё раз: если мы редиректимся через http header, то сохраняется оригинальный referer. Тест: vaara.ru/tmp.php.

      Правильно ли я понимаю, что, таким образом, описанная уязвимость сработает только при наличии редиректа через JS, HTML-форму, Meta refresh и т. п.? Про OAuth не знаю, но при реализации OpenID, как я помню, такие редиректы не требуются. Вообще, уже и не помню, когда последний раз писал редирект НЕ через http. На большинстве сайтов это банально не нужно.

      Если в чём-то ошибаюсь, поправьте.


      1. powerman
        11.05.2015 18:20
        +3

        если мы редиректимся через http header, то сохраняется оригинальный referer
        Обычно, если это Location:, то да. Но если это Refresh:, то нет. Плюс есть и другие способы: Referer hiding.

        Но моя изначальная мысль была в том, что человек делающий защиту от CSRF для всего сайта не может контролировать как именно будет реализован редирект на сайте через год. Допустим, ещё можно сказать, что HTTP-редиректы делаются только через Location:, но кто может заранее гарантировать, что на сайте никогда не будет ни одного JS меняющего document.location или автоматически отправляющего форму и т.п.? В вопросах безопасности лучше изначально не закладывать такие «мины».


  1. powerman
    11.05.2015 08:59
    +1

    Что касается отказа от Http-only кук и XSS в 5-м варианте — эту проблему можно обойти используя две разные куки: одну Http-only, вторую нет.

    Лично мне очень не нравится требование во вводной к наличию JS в браузере. У меня, например, NoScript и я очень не люблю отключать его на сайтах без веской причины — и работа обычных ссылок/форм обычно такой не является. Проекты бывают разные, если у Вас одностраничный сайт — понятно, что без JS он работать не будет в принципе и такое ограничение вводной может быть разумно. Но подход к решению CSRF хочется иметь универсальный, чтобы не пришлось всё переделывать если клиент в какой-то момент скажет «а вот этот раздел сайта очень важен и должен быть доступен всем, в т.ч. юзерам без JS/поисковикам/etc.». Если что-то может работать правильно без JS — не стоит от этой возможности отказываться без веских причин. Защита на токенах надёжна и будет работать без JS.


    1. Kroid
      11.05.2015 10:08
      +1

      Мне всегда было интересно, какой веской причиной руководствуются люди, которые полностью отключают js в своем браузере? И я имею ввиду сценарий повседневного использования, а не отдельные редкие ситуации, когда входишь в интернет через впн из под виртуалки.


      1. powerman
        11.05.2015 17:44
        +5

        А кто говорил про полное отключение? Чтобы полностью отключить JS никакой NoScript не нужен, это делается штатными настройками любого браузера. Я включаю JS там, где он нужен мне. Есть масса сайтов где JS используется для реализации функциональности, которая лично мне не нужна (более того, нередко эта функциональность только портит вид сайта, как ни поразительно — походите по инету с NoScript и вы удивитесь).

        Что касается причин — их несколько: превентивная защита от большинства браузерных эксплойтов (в т.ч. встроенных в страницы легального сайта после его взлома), экономия ресурсов системы (когда в браузере море открытых табов/окон и на большинстве из них JS отключен — это очень заметно улучшает отзывчивость браузера), дополнительная защита от отслеживания. Кроме того NoScript не только блокирует JS, он защищает и от других проблем (напр. от XSS и Clickjacking).


    1. kr41
      11.05.2015 10:43
      +3

      А вот зря powerman'а минусуют. Требовать JS там, где можно без него — плохая практика. И дело тут даже не в NoScript. Это просто ненадежно. У меня был случай, когда у твиттера упал CDN. Страница есть, а JS не пришел. В итоге сайт работал в read-only режиме. А еще в JS может быть ошибка, тоже много раз видел такое. Отдельно бесит использовать такие сайты через 3G-модем — страница загрузилась, а навигация не работает, т.к. не подгрузился JS. Как пример последнего — сайт AngularJS. Хорошая статья на эту тему Stop breaking the web (перевод здесь).


      1. Blumfontein
        11.05.2015 11:43
        +4

        >> Требовать JS там, где можно без него — плохая практика

        Ну да, зачем в машинах делают всякие круиз-контроли, зональные климат-контроли, плавающие сидения, можно просто 4 колеса, руль и ДВС, и так едет же.

        >> У меня был случай, когда у твиттера упал CDN

        Это проблема не жаваскрипта, а CDN и всех, кто его использует.

        >> страница загрузилась, а навигация не работает, т.к. не подгрузился JS

        Либо неправильно используется JS, либо у вас интернет медленный.

        Поддерживать пользователей без JS, то же самое, что поддерживать пользователей на ИЕ6, я считаю.


        1. kr41
          11.05.2015 12:12
          -2

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


          1. maximw
            11.05.2015 15:00

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


    1. gonzazoid Автор
      11.05.2015 19:50

      мне понравилась Ваша идея про две куки. Действительно, одна http-only, защищает от xss, вторая — токен, который дублируется скриптом в форме, защищает от csrf. Внесу в статью вместе с генерацией токена на основании session id.
      А вот насчет noscript use case, на мой взгляд Вы немного заблуждаетесь. При noscript хранить токен можно только в верстке, в самой форме, а это накладывает ограничения по CORS, я писал об этом в статье. Просто хранение токена в куке без дублирования его в форме тоже ничего не дает.


      1. powerman
        11.05.2015 22:45
        +2

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

        Более того. XSS даст возможность вытащить токен из вёрстки. Поэтому лучше всего использовать не один токен на всю сессию юзера, а уникальные токены при каждом запросе (вероятность что XSS будет на странице с отправляемой формой всё же ниже, чем что она найдётся в принципе где-то на сайте). Правда, это потребует дополнительных ресурсов сервера чтобы хранить хоть какую-то дополнительную информацию по выданным токенам для защиты от replay attack.

        Поэтому, на мой взгляд, по умолчанию стоит использовать уникальные для каждого запроса токенты в вёрстке. А любые упрощения этого подхода использовать только тогда, когда для них есть серьёзные основания (вроде highload), отдавая себе при этом отчёт, что эти упрощения снижают безопасность (или лишают возможности работать без JS, где это имеет значение).


      1. vintage
        12.05.2015 15:33
        -1

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


        1. powerman
          12.05.2015 16:13
          +1

          Защита по возможности всегда должна быть эшелонированной. Если всё корректно экранируется — никакой XSS не будет. Но если кто-то где-то ошибётся — будет лучше если через эту дыру взломщик не получит всё и сразу.


          1. vintage
            12.05.2015 23:45

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


            1. powerman
              13.05.2015 00:08

              Я и не использую. И поэтому в своих приложениях обычно даже не заморачиваюсь Http-only куками — XSS у меня в коде сроду не было. Но мы не мой код обсуждаем, а в общем случае защиту лучше делать эшелонированную. К тому же, всегда есть вероятность что мой код через два года будет поддерживать кто-то не столь организованный и таки найдёт способ вывести в шаблон не экранированное значение… и тогда моё пренебрежение Http-only может выйти боком.


              1. vintage
                13.05.2015 01:41

                http-only спасёт разве что от угона сессии. И то, ничто не помешает через XSS нарисовать пользователю форму входа и получить его логин-пароль.


  1. armab
    11.05.2015 13:24
    +2

    Из комментариев становится понятно, что статьи чаще читают наискосок.


  1. dm9
    11.05.2015 15:31

    Улучшение решения 5: скрипту давать доступ к hash(session_id + ":" + login + ":" + long_secret). В этом случае с сервера прилетает как сессия (http only), так и наш токен, который будет подставляться джава-скриптом в форму, но будет при этом произвольной хеш-функцией от сессии. Плюсы — легко проверить на сервере, не храня в базе список валидных токенов.

    Замечания к статье.

    > (Решение #3.) «Надеюсь, отчаянность мысли хранить токен в куках объяснять не надо.»

    Почему же? Как раз дальше Вы к этому и приходите :-)

    > (Решение #1) «На мой взгляд, тот процент посетителей без хидеров формируется из: ...»

    Странный список. Из закладок и из почтового клиента идёт уход на HTTP-форму? Обычно сначала Вы приходите на сайт через GET, а потом формируете запрос, который требует подтверждения токеном (или чем-то ещё).

    У меня на сайтах сейчас стоит проверка referer (с запретом пустого, конечно). Как выше писали, есть тонкости, но решение работает в целом нормально. На десятки тысяч посещений приходится одно сообщение о том, что что-то не работает (ну, сколько-то ещё отваливаются, не сообщая мне). В целом, устраивает. Переделать хочется, но руки никак не дойдут.


    1. gonzazoid Автор
      11.05.2015 16:45

      не успел (я про себя). Тоже пришел к такому решению после публикации, как раз сейчас перебиваю rc4 под использование вместо хэша (sha, md5 тяжеловаты для меня).
      но, работая над этим паралельно пришел к такой мысли. Использование хэша от куки на ри условии выдачи хэша серверов и закрытии куки http-only снимает проблемы безопасности в случае XSS. Но что нам это дает на стороне сервера? Казалось бы, это очень элегантно, для проверки не надо дергать memcached или кто там у нас. Я тоже так решил, написал код, благо недолго, подошел к тестированию, и тут понял простую вещь. Если проверка пройдена (а в подавляющем большинстве случаев она будет пройдена, нас же не все время все подряд атакуют), так вот, если проверка пройдена, то следующим этапом мы делаем что? правильно, разворачиваем сессию, то есть дергаем memcached, файлы, sql, в общем где лежит то и дергаем. И тут получается что преимущества от вычисления токена по сравнению с хранением токена — нет. Так как если мы будем хранить токен вместе с сессией, то можем сравнивать без тяжелых вычислений. В общем мысль такая — вычислять токен хэшем от сессии — очень красиво, но совершенно (на мой взгляд) непрактично, и имеет смысл только в абсолютно враждебном окружении, когда доля левых запросов занимает значимую часть в статистике (например при ddos-e). В обычных условиях — пока не вижу преимуществ. Но тем не менее, рассчитываю собрать все в кучу, уплрядочить и обновить статью, пусть будет, может кому пригодится.


      1. dm9
        11.05.2015 16:50

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


        1. gonzazoid Автор
          11.05.2015 17:05

          код отвечающий за хранение предлагаю вычеркнуть из списка Ваших аргументов — сессию то мы храним, +1 переменная — ни о чем. А вот генерация и валидация. Не готов согласиться, но и отстаивать свою точку зрения тоже что то сдерживает. Давайте так — я сейчас добью код, реализующий эту схему — и тогда уже можно будет предметно поговорить.
          (на первый взгляд генерация токена не может быть сложнее вычисления хэша, а сравнение готового хранящегося токена — должно быть проще сравнения с вычисляемым, но тут в любом случае щупать надо. Я для ноды развернул rc4, то есть вычисление идет без предварительной инициализации s таблицы, возможно это решение будет и простым и быстрым, но не тестил еще.)


      1. dm9
        11.05.2015 17:04

        Что касается тяжёлых вычислений — не знаю, что у вас там за хайлоад, но взять md5 — это единицы микро(!)секунд. Учитывая ещё, что CSRF-токеном защищаются запросы на модификацию или тяжёлые вычисления, это не просто копейки, а ничто.


        1. gonzazoid Автор
          11.05.2015 17:08

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


      1. mwizard
        11.05.2015 17:11

        Попробуйте, кстати, BLAKE2 вместо RC4. Он быстрее, чем RC4, и криптографически более устойчив.


        1. gonzazoid Автор
          12.05.2015 00:00

          э, нет. BLAKE2 быстрее md5, но он не может быть в принципе быстрее rc4. rc4 — потоковый шифр, и после разворачивания s таблицы затраты на шифрование — сложение по модулю 256, инремент по модулю 256, одна перестановка и один xor на каждый байт шифртекста (как при шифровании так и при расшифровке)
          Код у меня готов, не знаю куда его. В статью — не подходит, слишком спорный, в коменты — мало людей увидит, хотелось обсудить, на отдельную статью не тянет.


    1. gonzazoid Автор
      11.05.2015 16:47

      Почему же? Как раз дальше Вы к этому и приходите :-)

      Ну как же, повнимательней прочитайте. Я не предлагаю хранить и токен и сессию в куках. Там чуть чуть другое, и в этом чуть чуть — вся соль.


    1. gonzazoid Автор
      11.05.2015 16:49

      Странный список. Из закладок и из почтового клиента идёт уход на HTTP-форму?

      Не не не. Речь то о заходах пользователей без реферера, контекст — статистика посещений, в том месте я говорю не об атаке.


      1. dm9
        11.05.2015 16:54

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


  1. Qualab
    11.05.2015 15:42
    +1

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


    1. gonzazoid Автор
      11.05.2015 16:57

      это да, просто во многих статьях часть деталей упускают, и в итоге не очень понятно что именно и от кого защищаем. Мне вот например тот момент что токен можно отдавать только при авторизации — не попадался (не кто тому что я самый умный и единственный кто до этого додумался, а к тому что есть куча статей где это не оговорено ) а на этом ведь некисло можно подскользнуться.


  1. mezastel
    11.05.2015 16:06
    +6

    А что за иероглифы в начале поста?


    1. gonzazoid Автор
      11.05.2015 16:53
      -5

      1. mwizard
        11.05.2015 17:31
        +10

        А что они делают в технической статье?


      1. imwode
        11.05.2015 17:51
        +3

        Вот не начал каммент с иероглифа, получай минус


      1. aureliano_b
        12.05.2015 09:03
        +2

        Неужто мракобесие и до Хабры докатилось? Или это такая замысловатая ирония?


        1. grossws
          12.05.2015 12:07

          Это CORS для определенной категории читателей.


        1. powerman
          12.05.2015 16:09
          -3

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


          1. aureliano_b
            12.05.2015 17:46
            +6

            Очевидного вреда вроде бы нет. Но выглядит это тут по меньшей мере хм..., скажем, странно. Примерно как попы в МИФИ.


          1. vintage
            12.05.2015 23:57
            +5

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


            1. aureliano_b
              13.05.2015 05:17
              +1

              Обратите внимание, что в предыдущей первой публикации автора ничего такого в помине нет. Наверное забыл.
              Хотя что-то мне подсказывает, что автор разумно решил, что столь откровенная ссылка на иррациональную белиберду скажется на результатах оценки статьи в «песочнице».

              И да, не надо молчать. Это симптоматичный пример. Мракобесие маленькими шагами возвращается под молчаливое согласие тех, кто не хочет этого замечать. Смысл моей реплики, разумеется, не в том, чтобы очернить автора. Но чтобы указать на абсурдность его воззрений и неприличность демонстрировать эти воззрения в обществе здравомыслящих людей, к коим, я смею полагать, причисляют себя в большинстве своем посетители, читатели и участники сообщества habrahabr.


          1. darkdaskin
            13.05.2015 14:37
            +4

            Небольшой вред всё же есть

            Хотя не думаю, что автор добивался такого результата.


            1. gonzazoid Автор
              13.05.2015 16:45

              арабский язык, направление справа на лево. Это разрабам хабра стоит озадачиться — видимо направление меняется для всего содержимого тега.


  1. lucky_libora
    11.05.2015 19:30
    +1

    1. gonzazoid Автор
      11.05.2015 20:01
      +1

      We recommend that the token is a digest of your site's authentication cookie with a salt for added security.

      Согласен, монополию на здравый смысл не установишь. Хех, в одном абзаце суть всей статьи.


  1. RomanPyr
    11.05.2015 22:17
    +1

    JSON Web Token?


    1. powerman
      11.05.2015 22:32

      Штука хорошая, но в highload лишняя криптография вместо простого сравнения значения — это перебор.


  1. SerJook
    12.05.2015 10:15

    Почему бы это не запретить на уровне браузера


  1. Chikey
    15.05.2015 01:32
    +1

    При проектировании защит берется во внимание число пользователей которое можно этим покрыть и модель угроз. Во первых реферер не создавался как секюрити механизм и если найдут способ подменять реферер через какой нибудь флеш или плагин или баг в браузере, то вероятность этого больше чем полных SOP обход который нужен для чтения csrf token. А во вторых зачем терять маленький процент пользователей без реферера когда есть отработанный годами подход с токеном. Короче говоря среди защит выживает самая оптимальная.