Double submit что это
Double Submit Cookie Pattern
Previously, I have discussed the Synchronizer Tokens Pattern as one of the solutions for the Cross Site Request Forgery attack on Web Applications.
This blog post will discuss Double Submit Cookie Pattern to prevent from CSRF attack.
Double submitting cookies is defined as sending a random value in both a cookie and as a request parameter, with the server verifying if the cookie value and request value are equal.
When a user authenticates to a site, the site should generate a session identifier and set a cookie in the browser. At the same time, it generates the cryptographically strong random value or the CSRF token for the session and set it as a cookie on the user’s machine separate from the session id. The server does not have to save this value in any way, that’s why this pattern is sometimes also called Stateless CSRF Defense.
The site then requires that every request include this random value as a hidden form value (or another request parameter). A cross-origin attacker cannot read any data sent from the server or modify cookie values, per the same-origin policy.
In the case of this mitigation technique the job of the client is very simple; just retrieve the CSRF cookie from the response and add it into a special header to all the requests.
Let’s look a sample project,
This application is developed using PHP & JS( Github link — click here)
First, you need to login to the application by entering username and password. For the demo, I have hardcoded the credentials(username: admin, password: ssd2)
This login form submits user credentials using a POST method. if the user is authenticated successfully, server-side will creates a unique Session-Id and the CSRF token but the server only stores the Session-Id. Importantly server doesn’t store CSRF token in this scenario.
Then the server will response the corresponding CSRF token along with the response body. After that generated session id & server respond CSRF token set as cookies in the browser.
In here we must set the httponly flag “false” because js should able to access the csrf token cookie to add to the hidden field in the post request.
Множественные submit для формы
С приходом HTML5 формы сталее более универсальными. Элемент input теперь может содержать электронные адреса, даты и много другое, их можно отмечать как обязательные не прибегая к javascript – и это всего лишь некоторые из наиболее ценных возможностей. Также теперь для одной формы можно задействовать несколько submit кнопок, а также теперь есть возможность вынести кнопку submit за пределы формы.
Несколько submit внутри одной формы
До недавнего времени в форму можно было вставить только одну кнопку submit, в противном случае форма обрабатывала только последнюю кнопку. Добавляя method=»post» и url к элементу формы «form» мы получали рабочую форму.
Теперь ситуация изменилась – в HTML добавили новые свойства «formmethod» и «formaction». Они позволяют добавить метод post и url непосредственно в кнопку «submit», таким образом к form ничего дописывать не нужно. Имея эти параметры, прикрепленные к кнопке «submit», а не к form – все это добавляет больше гибкости форме. Теперь можно сделать столько кнопок, сколько будет необходимо для формы.
Теперь каждая кнопка «submit» относится к разным url и все это избавляет от того, что при верстке необходимо писать javascript код. Все это отлично работает и теперь по нажатии на какую-нибудь кнопку форма получит formmethod и formaction, которые перезапишут стандартные параметры method и action. Если в форме будет присутствовать обычная кнопка «submit» без новых параметров, то он вернет форме значения, установленные для элемента form.
Свойства formmethod и formaction поддерживаются всеми популярными браузерами
Элементы формы (input, select, textarea) за пределами формы
Общепринятый факт, что все элементы формы, принадлежащие ей должны находится внутри элемента
На сегодняшний день аттрибут form поддерживается всеми популярными браузерами, за исключением Internet Explorer (вплоть до 10й версии).
Методы защиты от CSRF-атаки
Что такое CSRF атака?
Ознакомиться с самой идеей атаки CSRF можно на классических ресурсах:
Выдержка из ответа на SO:
Как от нее защититься?
Эффективным и общепринятым на сегодня способом защиты от CSRF-Атаки является токен. Под токеном имеется в виду случайный набор байт, который сервер передает клиенту, а клиент возвращает серверу.
Защита сводится к проверке токена, который сгенерировал сервер, и токена, который прислал пользователь.
А что, собственно, защищать?
На Habrahabr опубликована статья от Yandex, в которой описано, почему писать свои сервисы нужно, руководствуясь стандартом.
Требования к токену:
На первом MeetUp’е PDUG Тимур Юнусов (руководитель отдела безопасности банковских
систем Positive Technologies) рассказывал, почему именно такие требования предъявляются к CSRF-Токену и чем грозит пренебрежение ими.
Требования к Web-Сервису и окружению:
Отсутствие XSS уязвимостей
Внедренный злоумышленником скрипт имеет возможность отправлять запрос к серверу от имени пользователя и читать его без каких-либо препятствий.
Таким образом, XSS уязвимости могут быть использованы для получения текущего токена.
Отсутствие malware на машине клиента
Если злоумышленник имеет возможность запускать софт на машине клиента, то он может получить любые данные, имеющиеся в браузере.
Методы защиты
Существует 3 метода использования токенов для защиты web-сервисов от CSRF атак:
Synchronizer Tokens
Простой подход, использующийся повсеместно. Требует хранения токена на стороне сервера.
При старте сессии на стороне сервера генерируется токен.
Токен кладется в хранилище данных сессии (т.е. сохраняется на стороне сервера для последующей проверки)
В ответ на запрос (который стартовал сессию) клиенту возвращается токен.
Если рендеринг происходит на сервере, то токен может возвращаться внутри HTML, как, например, одно из полей формы, или внутри тега.
В случае, если ответ возвращается для JS приложения, токен можно передавать в header (часто для этого используют X-CSRF-Token )
При последующих запросах клиент обязан передать токен серверу для проверки.
При рендере контента сервером токен принято возвращать внутри POST данных формы.
JS приложения обычно присылают XHR запросы с header ( X-CSRF-Token ), содержащим токен.
Если оба токена совпадают, то запрос не подвергся CSRF-Атаке, в ином случае — логируем событие и отклоняем запрос.
На выходе имеем:
Защита от CSRF на хорошем уровне
Токен обновляется только при пересоздании сессии, а это происходит, когда сессия истекает
Во время жизни одной сессии все действия будут проверяться по одному токену.
Если произойдет утечка токена, то злоумышленник сможет выполнить CSRF-Атаку на любой запрос и в течение долгого срока. А это не есть хорошо.
Бесплатная поддержка multi-tab в браузере.
Токен не инвалидируется после выполнения запроса, что позволяет разработчику не заботиться о синхронизации токена в разных табах браузера, так как токен всегда один.
Double Submit Cookie
Этот подход не требует хранения данных на стороне сервера, а значит, является Stateless. Используется, если вы хотите уметь быстро и качественно масштабировать свой Web-сервис горизонтально.
Идея в том, чтобы отдать токен клиенту двумя методами: в куках и в одном из параметров ответа (header или внутри HTML).
При запросе от клиента на стороне сервера генерируется токен. В ответе токен возвращается в cookie (например, X-CSRF-Token ) и в одном из параметров ответа (в header или внутри HTML).
В последующих запросах клиент обязан предоставлять оба полученных ранее токена. Один как cookie, другой либо как header, либо внутри POST данных формы.
Если оба токена совпадают, то запрос не подвергся CSRF-Атаке, в ином случае — логируем событие и отклоняем запрос.
На выходе имеем:
Stateless CSRF защиту.
Таким образом, если ваш сервис доступен на домене 3-го уровня, а злоумышленник имеет возможность зарегистрировать свой ресурс на вашем домене 2-го уровня, то устанавливайте cookie на свой домен явно.
Encrypted Token
Так же как и Double Submit, является Stateless подходом. Основная — если вы зашифруете надежным алгоритмом какие-то данные и передадите их клиенту, то клиент не сможет их подделать, не зная ключа. Этот подход не требует использования cookie. Токен передаётся клиенту только в параметрах ответа.
В данном подходе токеном являются факты, зашифрованные ключом. Минимально необходимые факты — это идентификатор пользователя и timestamp времени генерации токена. Ключ не должен быть известен клиенту.
При запросе от клиента на стороне сервера генерируется токен.
Генерация токена состоит в зашифровке фактов, необходимых для валидации токена в дальнейшем.
Минимально необходимые факты — это идентификатор пользователя и timestamp. В ответе токен возвращается в одном из параметров ответа (В header или внутри HTML).
В последующих запросах клиент обязан предоставлять полученный ранее токен.
Валидация токена заключается в его расшифровке и сравнения фактов, полученных после расшифровки, с реальными. (Проверка timestamp необходима для ограничения времени жизни токена)
Если расшифровать не удалось либо факты не совпадают, считается, что запрос подвергся CSRF-Атаке.
На выходе имеем:
Stateless CSRF защиту
Нет необходимости хранить данные в cookie
О реализации
Давайте генерировать новый токен на каждый запрос, не важно, каким HTTP-методом и с какой целью этот запрос сделан.
Таким образом мы получаем токен, который меняется постоянно.
Конечно, возникает вопрос организации multi-tab работы.
Синхронизация токенов между табами может быть реализована с использованием localStorage и его StorageEvent
Ограничиваем время жизни cookie, которое содержит токен, разумным значением. Например 30 минут.
Делаем cookie недоступной из JS (ставим HTTPOnly=true)
Используем TLS для предотвращения MITM
При этом отправляем cookie только по HTTPS (ставим Secure=true)
Размер токена не менее 32 байт.
Генерируем токен криптографически стойким генератором псевдослучайных чисел.
Для этого можно использовать системные функции:
Что еще нужно знать?
Токены — обязательная защита от CSRF.
Проверяйте, но не полагайтесь только на X-Requested-With: XMLHttpRequest
Не передавайте токены в URL
Same Site
Сейчас идет работа над спецификацией атрибута «Same-Site» у cookies (последняя версия на момент написания статьи).
Такой атрибут даст возможность разработчикам явно указывать, что cookie не нужно передавать, если запрос идет с сайта, отличного от того, на котором cookie была установлена. А, значит, у нас появится возможность защищать ресурсы от CSRF без использования дополнительных инструментов.
Браузер Chrome уже сейчас поддерживает эту возможность.
Чуть больше информации о том, как и почему доступно на Stack Exchange.
CSRF-уязвимости все еще актуальны
CSRF (Сross Site Request Forgery) в переводе на русский — это подделка межсайтовых запросов. Михаил Егоров (0ang3el) в своем докладе на Highload++ 2017 рассказал о CSRF-уязвимостях, о том, какие обычно используются механизмы защиты, а также как их все равно можно обойти. А в конце вывел ряд советов о том, как правильно защищаться от CSRF-атак. Под катом расшифровка этого выступления.
О спикере: Михаил Егоров работает в компании Ingram Micro Cloud и занимается Application security. В свободное время Михаил занимается поиском уязвимостей и Bug hunting и выступает на security-конференциях
Дисклаймер: приведенная информация является сугубо мнением автора, все совпадения случайны.
В том, что CSRF-атаки работают виноват этот Cookie-монстр. Дело в том, что многие веб-приложения используют куки (здесь и далее считаем уместным называть cookies по-русски) для управления сессией пользователя. Браузер устроен так, что, если у него есть куки пользователя для данного домена и пути, он их автоматически отправляет вместе с HTTP-запросом.
Cookies
Куки — это небольшой фрагмент данных, который веб-сервер отправляет клиенту в виде name=value в HTTP-заголовке c названием «Set-Cookie». Браузер хранит эти данные на компьютере пользователя, и всякий раз при необходимости пересылает этот фрагмент данных веб-серверу в составе HTTP-запроса в HTTP-заголовке с названием «Cookie».
Куки могут иметь различные атрибуты, такие как: expires, domain, secure, httponly:
Впервые куки появились в браузере Netscape в далеком 1994 году. До сих пор многие веб-приложения используют их для управления сессией пользователя.
Рассмотрим, как работает классическая Сross Site Request Forgery (CSRF) атака.
Допустим, в нашем веб-приложении есть возможность изменять адрес доставки у пользователя, и оно использует куки для управления сессией.
У нас есть HTML-форма, которую пользователь должен заполнить: ввести адрес и нажать кнопку «Сохранить». В результате в бэкенд полетит POST-запрос с HTML-формой. Мы видим, что браузер автоматически поставил туда сессионные куки пользователя. Бэкенд, когда получит такой запрос, посмотрит, что есть такая сессия, это легитимный пользователь, и изменит ему адрес доставки.
Что может сделать атакующий?
Он может на своем сайте attacker.com разместить такую HTML-страничку, которая на самом деле сабмитит HTML-форму на сайт example.com. Так как браузер автоматически вставляет куки пользователя в HTTP-запрос, то бэкенд просто не поймет, является ли данный запрос легитимным — результат ли это заполнения формы пользователем, или это CSRF-атака — и поменяет адрес доставки для пользователя на значение, которое выгодно для атакующего.
Есть другой вариант CSRF-атаки с использованием XHR API. Если о CSRF-атаке с использованием HTML-форм слышали многие, то о данном способе знают меньше, но он тоже работает.
Обратите внимание на атрибут withCredentials, который заставляет браузер автоматически отправлять куки пользователя. Так как значение Content-type равно application/x-www-form-urlencoded, то браузер данный запрос отправит без CORS options preflight request, и опять CSRF-атака будет работать.
Рассмотрим более наглядно, как это происходит.
История CSRF-атак
Вообще о CSRF-атаках известно с 2001 года, когда их начали активно эксплуатировать. В период 2008-2012 такие уязвимости были на каждом первом сайте, в том числе:
Насколько серьезны CSRF-уязвимости?
На самом деле, все зависит от критичности уязвимого действия. Это может быть:
В проекте OWASP Top 10, который содержит 10 наиболее критичных уязвимостей в приложении, в 2010 году CSRF-уязвимости находились на 5 месте. Потом разработчики начали имплементировать различные варианты защиты и уже в 2013 году CSRF-уязвимости сместились на 8 позицию.
В список за 2017 год CSRF-уязвимости вообще не вошли, потому что якобы по статистике сейчас при penetration-тестировании они находятся только в 8% случаев.
Лично я не согласен с данной статистикой, потому что буквально за последние два года находил много CSRF-уязвимостей. Дальше я расскажу, как я это делал.
В классификации Bugcrowd VRT (Vulnerability Rating Taxonomy) Application-wide CSRF-уязвимости имеют рейтинг severity P2 (High). Выше только severity critical, то есть это достаточно серьезные уязвимости.
Рассмотрим, какие варианты защиты от CSRF существуют и как работает каждый из вариантов защиты.
Поэтому, теперь давайте поговорим о 8 способах обхода защиты, которые можно использовать на практике.
Сценарии обхода:
1. XSS (cross-sitescripting)
Если в вашем веб-приложении есть XSS, то это автоматически делает его уязвимым к CSRF, и от этого сложно защититься. Можно только смириться.
2. Dangling markup
Допустим, в нашем приложении есть уязвимость к HTML injection, но нет XSS. Например, есть Content Security Policy (CSP), которая защищает от XSS. Но атакующий все равно может внедрять HTML теги.
Если в нашем приложении реализована защита, основанная на CSRF-токенах, атакующий может внедрить такой HTML, это не закрытые теги image или form:
В результате часть DOM HTML страницы будет отправлена на ресурс атакующего. Высока вероятность того, что если атакующий правильно внедрит такой HTML, тогда то, что придет на сайт атакующего, будет содержать CSRF-токен.
Таким образом узнав токен, атакующий сможет эксплуатировать CSRF классическим способом.
3. Уязвимый субдомен
Допустим, у нас есть субдомен foo.example.com, и он уязвим к subdomain takeover или XSS. В результате subdomain takeover, атакующий полностью контролирует субдомен и может добавлять туда любые HTML-странички или выполнять JS-код в контексте субдомена. Если наш субдомен уязвим к таким вещам, то атакующий сможет обойти следующие типы CSRF-защиты:
Следующий вариант. Допустим, на основном домене, который мы хотим атаковать, есть файл crossdomain.xml. Этот файл используется flash- и PDF-плагинами для субдоменного взаимодействия, и к нему разрешен доступ с любых субдоменов.
Если атакующий может загрузить JS-файл на foo.example.com, то в этом случае он может использовать Service Worker API для субдомена foo.example.com, который на самом деле отдает flash-файл.
Так как у нас есть crossdomain.xml на основном домене, который разрешает взаимодействие субдоменов, то атакующий через этот SWF просто читает CSRF токен.
Кстати, подобная уязвимость недавно была найдена в Amazon, подробнее здесь.
Даже если не сконфигурирован CORS и нет файла crossdomain.xml, но используется защита Double submit cookie, атакующий может просто вставлять куки с субдомена для родительского домена на путь, где он хочет эксплуатировать CSRF, и таким образом обойти Double submit cookie защиту.
4. Bad PDF
Этот сценарий обхода основан на PDF. В Adobe есть PDF плагин, который автоматически устанавливается при установке Adobe Reader. Этот плагин поддерживает так называемый FormCalc скрипт. Правда, сейчас PDF плагин от Adobe работает только в IE11 и Firefox ESR.
В FormCalc есть два замечательных метода: get() и post(). Атакующий с помощью метода get может прочитать CSRF токен, с помощью post отправить к себе на сайт. Так атакующий получает CSRF-токен жертвы.
Допустим, у нас есть возможность загрузить PDF-файл в веб-приложение. На самом деле это может быть даже файл другого формата, например, атакующий может попытаться загрузить PDF под видом картинки, которая является аватаром пользователя.
У приложения есть некоторый API на основном домене, который позволяет получать содержимое загруженного файла. Тогда атакующий может использовать такую HTML страничку, которая с помощью тега embed встраивает PDF-файл, который атакующий загрузил на example.com.
Файл leak.pdf:
Этот файл содержит FormCalc скрипт, который как раз читает страницу Settings.action, где в DOM есть CSRF токен и отправляет с помощью метода post на сайт атакующего.
Дополнительный фокус в том, что для PDF плагина не важно, с каким Content-Type отдается PDF-файл, и даже в HTTP-ответе могут присутствовать другие заголовки (например, Content-Disposition). PDF плагин все равно будет рендерить этот PDF и выполнять FormCalc скрипт.
5. Cookie injection
Если используется Double submit cookie защита, то если атакующий сможет каким-либо образом внедрить куки, то это game over.
Один из наиболее популярных вариантов в этом сценарии — это CRLFinjection.
Если атакующий может вставлять в ответ сервера дополнительные заголовки, то он просто может добавить заголовок Set-Cookie с нужными куками и обойти CSRF-защиту.
Другой вариант связан с особенностями обработки куков браузером.
Например, в Safari можно через запятую вставлять новые куки (comma-separated cookies). Допустим, у нас есть URL-параметр в заголовке с именем language. Мы его обрабатываем и записываем пользователю в куки выбранное значение language. Если атакующий вставит запятую, то он может вставить и дополнительные куки с любым именем.
Также в обходе CSRF-защиты могут посодействовать баги браузера. Например, в Firefox была возможность внедрять куки через SVG-картинку (CVE-2016-9078). Если у нас есть HTML-редактор и мы разрешаем пользователю вставлять image-теги, то атакующий может просто в SRC-атрибуте указать на SVG-картинку, которая установит нужные куки.
6. Change Content-Type
Некоторые разработчики считают, что если используется нестандартный формат данных в теле POST-запроса для общения с бэкендом, то это может спасти от CSRF. На самом деле это не так.
В качестве примера приведу уязвимость, которую я недавно нашел в одном очень популярном сервисе по управлению заметками.
Там использовался API, который использует Apache Thrift (бинарный формат данных) и куки для управления сессией. Допустим, чтобы добавить новую заметку, пользователь должен был отправить такой POST-запрос. В теле передавались бинарные данные и указывался Content-Type: application/x-thrift.
На самом же деле в бэкенде этот Content-Type не валидировался. Можно было его поменять на text/plain и с помощью XHR API эксплуатировать эту CSRF-уязвимость, просто передав бинарные данные в теле POST-запроса.
На самом деле защита, основанная на Content-Type — это очень плохой вариант защиты. Он в большинстве случаев обходится.
7. Non-simple Content-Type
Через HTML-форму или с помощью XHR API мы можем отправить следующие content types:
Довольно известный баг в браузере Chrome был найден в 2015 году, после этого примерно через месяц попал в публичный доступ, но был исправлен только в 2017 году. Этот баг позволял отправлять POST-запрос с любым Content-Type на другой origin с помощью API, которое называется Navigator.sendBeacon().
Как выглядела эксплуатация?
Мы создаем новый blob с нужным Content-Type и просто отправляем его с помощью Navigator.sendBeacon().
Еще один сценарий обхода, который до сих пор работает и поддерживается в браузерах — обход с помощью flash-плагина.
Даже есть сайт thehackerblog.com, где уже есть готовая флэшка, вы просто указываете URL, header, нужный Content-Type и данные, которые надо передать — отправляете, и в бэкенд улетает POST-запрос с нужным Content-Type.
Но есть одна хитрость — нельзя просто указать URL сайта, который мы атакуем. Нужно указать ресурс, который сделает redirect с кодом 307 на ресурс, который мы атакуем. Тогда это будет работать.
8. Spoof Referer
Последний вариант обхода защиты от CSRF основан на Referer. Есть баг в Microsoft Edge браузере, который до сих пор не исправлен и позволяет подделывать значение Referer. Но он работает, к сожалению, только для GET-запросов. Если атакуемый бэкенд не отличает GET от POST, то этот баг можно эксплуатировать.
Если все же нам надо POST, то есть небольшая хитрость. Мы можем отправить header Referer с помощью PDF плагина и FormCalc.
Где-то год назад можно было с помощью PDF плагина отправлять вообще любые header’ы, в том числе host, но потом Adobe закрыл эту возможность создав черный список header’ов. То есть если мы укажем Referer в заголовке, то этот заголовок просто не отправится.
Вообще FormCalc позволяет нам легально отправлять любой Content-Type. Если мы будем вставлять символы carridge return и line feed, то сможем добавлять в запрос дополнительные header’ы.
Это итоговая таблица. В столбцах представлены варианты защиты от CSRF, а в строках — методы обхода. В каждой ячейке указаны браузеры, в которых работает этот метод:
Наиболее кардинальный и работающий вариант защититься от CSRF-атак — это избавиться от куков и использовать header с токенами.
Но если вы все-таки не готовы отказаться от куков для управления пользовательской сессией:
Не прошло и полугода, а следующий хайлоад уже через месяц — Highload++ Siberia.
Хотим привлечь ваше внимание к некоторым из отобранных докладов: