Пер. Под катом вас ждет перевод смешноватой и несложной статьи о CSRF и новомодном способе защиты от него.
Древний змий
Уязвимость CSRF или XSRF (это синонимы), кажется, существовала всегда. Её корнем является всем известная возможность делать запрос от одного сайта к другому. Допустим, я создам такую форму на своем сайте.
<form action="https://bankingsite.com/transfer" method="POST" id="stealMoney">
<input type="hidden" name="to" value="John Doe">
<input type="hidden" name="account" value="12416234">
<input type="hidden" name="amount" value="$1,000">
Ваш браузер загрузит мой сайт и, соответственно, мою форму. Её я могу незамедлительно отправить, используя простой javascript.
document.getElementById("stealMoney").submit();
Поэтому подобная атака буквально расшифровывается, как межсайтовая подделка запроса. Я подделываю запрос, который отправляется между моим сайтом и вашим банком. В действительности, проблема состоит не в том, что я отправлю запрос, а в том, что ваш браузер отправит вместе с запросом и ваши куки. Это означает, что запрос будет обладать всеми вашими правами, так что, если вы залогинены в текущий момент на сайте вашего банка, то только что вы пожертвовали мне тысячу долларов. Данке шон! Если же вы не были залогинены, то деньги всё еще на месте. Существуют несколько способов защиты от подобных злостных посягательств.
Способы защиты от CSRF
Не буду вдаваться в детали о способах защиты, так как в интернете полным-полно информации о них, но давайте быстро пробежимся по основным реализациям.
Проверка источника
Принимая запрос в нашем приложении, потенциально мы можем узнать о том, откуда он пришел, посмотрев на два заголовка. Они называются origin и referer. Так что мы можем проверить один или оба значения, чтобы узнать, пришёл ли запрос с нашего приложения или откуда-то ещё. Если источник запроса не ваш сайт, то можно просто на него ответить ошибкой. Проверка этих заголовков может нас защитить, но проблема в том, что они не всегда могут присутствовать.
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-encoding: gzip, deflate, br
cache-control: max-age=0
content-length: 166
content-type: application/x-www-form-urlencoded
dnt: 1
origin: https://example.com
referer: https://example.com /login
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Защитные токены
Существуют два способа использования уникальных защитных токенов, но принцип их использования един. Когда пользователь посещает страницу, скажем, страницу банка, то в форму перечисления денег вставляется скрытое поле с уникальным токеном. Если пользователь действительно находится на сайте банка, то вместе с запросом он отправит этот токен, так что можно будет проверить, совпадает ли он с тем, что вы вставили в форму. При попытке CSRF атаки атакующий никогда не сможет получить это значение. Даже в случае запроса к странице Same Origin Policy (SOP) не позволит ему прочитать страницу, где есть данный токен. Такой метод хорошо себя зарекомендовал, однако он требует от приложения логики по внедрению токенов в формы и проверки их подлинности при входящих запросах. Еще один похожий метод заключается во внедрении такого же токена в форму и передачи куки, содержащего то же значение. Когда настоящий пользователь отправляет форму, то происходит сверка значения токена в куки со значением в форме. Если они не совпадают, то такой запрос не будет принят сервером.
<form action="https://report-uri.io/login/auth" method="POST">
<input type="hidden" name="csrf_token" value="d82c90fc4a14b01224gde6ddebc23bf0">
<input type="email" id="email" name="email">
<input type="password" id="password" name="password">
<button type="submit" class="btn btn-primary">Login</button>
</form>
Так в чем проблема?
Вышеописанные методы защиты давали нам достаточно надежную защиту против CSRF на протяжении довольно долгого времени. Конечно, проверка заголовков origin и referer не на 100% надежна, так что большинство сайтов полагается на тактику с уникальными токенами. Сложность состоит в том, что оба метода защиты требуют от сайта внедрения и поддержки решения. Вы скажете, что это совсем несложно. Согласен! Никогда не возникало проблем. Но это некрасиво. По сути мы обороняемся от поведения браузера хитрым способом. А можем ли мы просто сказать браузеру перестать делать вещи, которые мы не хотим, чтобы он делал?.. Теперь можем!
Same-Site Cookie
По сути Same-Site куки могут свести на нет любую CSRF атаку. Насмерть. Чуть более, чем полностью. Адьёс, CSRF! Они действенно и быстро помогают решить проблему безопасности. К тому же применить их чрезвычайно просто. Возьмем для примера какую-то куку.
Set-Cookie: sess=smth123; path=/
А теперь просто добавьте атрибут SameSite
Set-Cookie: sess=smth123; path=/; SameSite
Всё, вы закончили. Нет, правда! Используя данный атрибут, вы как-бы говорите браузеру предоставить куки определенную защиту. Существую два режима такой защиты: Strict или Lax, в зависимости от того, насколько серьезно вы настроены. Атрибут Same-Site без указания режима будет работать в стандартном варианте т.е. Strict. Вы можете выставить режим так:
SameSite=Strict
SameSite=Lax
Strict
Такой режим предпочтительней и безопасней, но может не подойти для вашего приложения. Этот режим означает, что c ваше приложение не будет отправлять куки ни на один запрос с другого ресурса. Само собой в таком случае CSRF не будет возможен в корне. Однако здесь можно столкнуться с проблемой, что куки не будут пересылаться также при навигации высокого уровня (т.е. даже при переходе по ссылке). Например, если бы я сейчас разместил ссылку на Вконтакте, а Фейсбук использовал куки Вконтакте, то при переходе по ссылке вы бы оказались разлогинены, вне зависимости от того, были ли вы залогинены до этого. Такое поведение, конечно, может не порадовать пользователя, но можно быть уверенным в безопасности.
Что можно сделать с этим? Можно поступить, как Амазон. У них реализована как-бы двойная аутентификация с помощью двух куки. Первый куки позволяет амазону просто знать, кто вы и показывать вам ваше имя. Для него не используется SameSite. Второй куки позволяет делать покупки, менять что-то в аккаунте. Для него резонно используется SameSite. Такое решение позволяет одновременно предоставлять пользователям удобство и оставаться приложению безопасным.
Lax
Режим Lax решает проблемы с разлогированием описанную выше, но при этом сохраняет хороший уровень защиты. В сущности он добавляет исключение, когда куки передаются при навигации высокого уровня, которая использует “безопасные” HTTP методы. Согласно https://tools.ietf.org/html/rfc7231#section-4.2.1 безопасными методами считаются GET, HEAD, OPTIONS и TRACE.
Вернемся к нашему примеру атаки в начале.
<form action="https://bankingsite.com/transfer" method="POST" id="stealMoney">
<input type="hidden" name="to" value="John Doe">
<input type="hidden" name="account" value="12416234">
<input type="hidden" name="amount" value="$1,000">
Такая атака уже не сработает в режиме Lax, так как мы обезопасили себя от POST-запросов. Конечно, злоумышленник может использовать метод GET.
<form action="https://bankingsite.com/transfer" method="GET" id="stealMoney">
<input type="hidden" name="to" value="John Doe">
<input type="hidden" name="account" value="12416234">
<input type="hidden" name="amount" value="$1,000">
Поэтому режим Lax можно назвать компромиссом между безопасностью и удобством пользователей.
SameSite куки защищают нас от CSRF атак, но нельзя забывать про другие виды уязвимостей. К примеру XSS или браузерные атаки по времени.
От автора перевода: К сожалению, пока SameSite куки поддерживают только Chrome и Opera, а также браузер для андроида. Пруф: https://caniuse.com/#feat=same-site-cookie-attribute
Оригинал: https://www.kuoll.com/the-end-of-csrf/
До этого появлялась здесь: https://scotthelme.co.uk/csrf-is-dead/
Автор: Kirylka