Как защитить свой сайт с помощью Anti-CSRF токенов
Перевод статьи: Netsparker Security Team — How to Protect Your Website Using Anti-CSRF Tokens
Токены Anti-CSRF (или просто токены CSRF) — это уникальные значения, используемые в веб-приложениях для предотвращения атак с подделкой межсайтовых запросов (CSRF / XSRF). CSRF-атаки — это атаки на стороне клиента, которые могут использоваться для перенаправления пользователей на вредоносный веб-сайт, кражи конфиденциальной информации или выполнения других действий в рамках сеанса пользователя. В этой статье рассказано, как использовать токены CSRF для защиты пользователей от атак CSRF и их последствий.
Основы Anti-CSRF токенов
Основной принцип, лежащий в основе токенов anti-CSRF (также известных как шаблоны токенов синхронизатора), заключается в том, чтобы предоставить браузеру пользователя уникальную информацию (токен) и проверить, отправляет ли веб-браузер ее обратно. Токен должен быть уникальным и его невозможно угадать третьим лицам. Приложение не должно работать, если оно не проверит эту информацию. Таким образом, только исходный пользователь может отправлять запросы в аутентифицированном сеансе.
Допустим, вы запускаете веб-приложение социальной сети на сайте www.example.com. Чтобы опубликовать сообщение в своем профиле, пользователь заполняет HTML-форму и нажимает кнопку «Отправить».
<form action="/action.php" method="post">
Subject: <input type="text" name="subject"/><br/>
Content: <input type="text" name="content"/><br/>
<input type="submit" value="Submit"/>
</form>
Это заставляет веб-браузер отправлять POST запрос:
POST /post.php HTTP/1.1
Host: example.com
subject=subject&content=content
Если пользователь вошел в систему и злоумышленник знает синтаксис этого запроса, злоумышленник может использовать CSRF-атаку для публикации рекламы в профиле пользователя:
<form action="/action.php" method="post">
Subject: <input type="text" name="subject" value="Buy my product!"/>
Content: <input type="text" name="content" value="To buy my product, visit this site: example.biz."/>
<input type="submit" value="Submit"/>
</form>
<script>
document.forms[0].submit();
</script>
В результате веб-браузер отправляет следующий POST запрос:
POST /post.php HTTP/1.1
Host: example.com
subject=Buy my product!&content=To buy my product, visit this site: example.biz.
Если ваш сайт использует простой токен anti-CSRF, веб-сервер устанавливает этот токен в cookie сеанса вашего веб-браузера сразу после входа в систему. Все отправленные формы через скрытое поле (hidden field), будут содержать этот токен. Таким образом это полностью устраняет уязвимость CSRF.
<form>
Subject: <input type="text" name="subject"/><br/>
Content: <input type="text" name="content"/><br/>
<input type="submit" value="Submit"/>
<input type="hidden" name="token" value="R6B7hoBQd0wfG5Y6qOXHPNm4b9WKsTq6Vy6Jssxb"/>
</form>
Затем сервер проверит, содержит ли каждый POST запрос требуемый токен:
POST /post.php HTTP/1.1
Host: example.com
subject=I am feeling well&content=I just ate a cookie and it was delicious.&token=R6B7hoBQd0wfG5Y6qOXHPNm4b9WKsTq6Vy6Jssxb
Если злоумышленник попытается выполнить подделку межсайтового запроса с помощью вредоносного сайта, он не узнает текущий токен, установленный в cookie. Ваш сервер не станет обрабатывать запрос без этого токена, поэтому атака не удастся.
Как генерировать и проверять токены
Когда вы создаете и позже проверяете свой токен, следуйте этим принципам, чтобы убедиться, что ваш anti-CSRF токен не может быть угадан или использован иным образом:
- Используйте непредсказуемый, хорошо зарекомендовавший себя генератор случайных чисел с достаточной энтропией.
- Срок действия токенов должен истекает через короткое время, чтобы их нельзя было повторно использовать.
- Используйте безопасные способы проверить, совпадает ли полученный токен с установленным токеном, например, сравнивая хэши.
- Не отправляйте токены CSRF в HTTP-запросах GET, чтобы они не были напрямую доступны в URL-адресе и не просочились в заголовок Referer с другой информацией о источнике.
Например, в PHP вы можете сгенерировать токен следующим образом:
$_SESSION['token'] = bin2hex(random_bytes(24));
И проверьте токен следующим образом:
if (hash_equals($_SESSION['token'], $_POST['token'])) {
// Action if token is valid
} else {
// Action if token is invalid
}
Anti-CSRF защита для форм
Описанный выше токен анти-CSRF устанавливается при входе в систему в файле cookie сеанса пользователя, а затем проверяется каждой формой. В большинстве случаев этой защиты достаточно. Однако некоторые сайты предпочитают использовать более безопасный подход. Чтобы достичь хорошего компромисса между безопасностью и удобством использования, вы можете создать отдельные токены для каждой формы.
Для этого создайте токен, но не открывайте его напрямую браузеру пользователя. Вместо этого хешируйте токен с именем файла формы, например так:
hash_hmac('sha256', 'post.php', $_SESSION['internal_token'])
Когда вы получите запрос, сравните хеши. Если токен действителен и текущая форма действительна, хеши будут совпадать.
Anti-CSRF защита для каждого запроса
Если вам нужен очень высокий уровень защиты, вы можете использовать отдельные токены для каждого запроса. Это просто реализовать: все, что вам нужно сделать, это сделать токен недействительным после его проверки.
У такого подхода есть несколько недостатков для пользователей. Например, пользователи, которым нравится работать с несколькими вкладками, не смогут этого больше сделать. Кнопка возврата также прервет поток. Поэтому, прежде чем рассматривать этот подход, убедитесь, что он не повлияет отрицательно на пользовательский опыт. При создании токенов защиты от CSRF атаках на каждый запрос также учитывайте производительность сервера и используйте менее ресурсоемкие генераторы случайных чисел.
Использование непостоянных токенов
Если ваша веб-страница или веб-приложение очень загружены, а хранилище на сервере ограничено, вы, вероятно, захотите избежать сохранения токенов на стороне сервера. В этих случаях вы можете генерировать и обрабатывать токены криптографически. При таком подходе нет необходимости хранить токен в сеансе сервера:
- Используйте симметричное шифрование с ключом, который известен только серверу и никогда не передается.
- Объедините текущую метку времени, имя пользователя и имя формы (при необходимости), а затем зашифруйте эту комбинацию с помощью ключа сервера.
- Когда вы получаете токен из веб-браузера, расшифруйте его с помощью того же ключа.
- Сравните метку времени из расшифрованного содержимого с текущей меткой времени (чтобы исключить старые токены), сравните расшифрованное имя пользователя с текущим именем пользователя и сравните имя расшифрованной формы с именем текущей формы.
Обратите внимание, что хотя этот метод поможет вам избежать хранения большого количества информации, но он может иметь накладные расходы на производительность, поскольку криптографические функции потребляют больше ресурсов, чем простая генерация случайных чисел.
Другой вариант для непостоянных токенов — файлы cookie с двойной отправкой. В этом методе сервер устанавливает случайное значение в файле cookie для пользователя еще до его аутентификации. Затем сервер ожидает, что это значение будет отправляться с каждым запросом (например, с использованием значения скрытой формы).
CSRF защита для Ajax
Токены Anti-CSRF также следует использовать для запросов Ajax. Однако перед тем, как реализовать какой-либо тип защиты CSRF для Ajax, убедитесь, что ваш веб-сервер не разрешает междоменные запросы Ajax (проверьте заголовки Cross-Origin Resource Sharing).
В случае Ajax вы можете включить свой токен в скрытое текстовое поле или непосредственно в JavaScript. Затем вы отправляете токен с каждым запросом Ajax и проверяете его присутствие на стороне сервера.
Anti-CSRF токены для формы входа
Принято считать, что Anti-CSRF токены необходимы только тогда, когда пользователь вошел в систему. Поэтому формы входа обычно не используют никакой защиты CSRF. Несмотря на то, что невозможно выдать себя за пользователя до того, как он войдет в систему, отсутствие защиты CSRF для форм входа может привести к тому, что пользователя обманом заставят войти в систему в качестве злоумышленника и раскрыть конфиденциальную информацию. Например, атака может быть проведена следующим образом:
- Злоумышленник создает учетную запись на вашем сайте.
- Злоумышленник обманом заставляет жертву войти на ваш сайт, используя свои учетные данные (это возможно, если для формы входа нет защиты CSRF).
- Жертва использует ваш веб-сайт и может не осознавать, что вошла в систему под другим пользователем.
- Злоумышленник может следить за жертвой с помощью функции истории или каким-либо другим способом и таким образом получить выгоду от использования жертвой вашего сайта.
По этим причинам рекомендуется также включать anti-CSRF токены на все страницы входа.
Предотвращение CSRF атак за пределами токенов
Для углубленной защиты от CSRF вы можете комбинировать токены CSRF с другими подходами. Например, вы можете использовать настраиваемые заголовки для проверки запросов Ajax. Этот метод работает, потому что в соответствии с политикой одного источника для добавления заголовков запроса можно использовать только JavaScript из одного источника. Подробное обсуждение этого и других методов предотвращения CSRF см. OWASP Cross-Site Request Forgery Prevention Cheat Sheet.
Токены Anti-CSRF — один из самых безопасных способов защиты от атак CSRF, но в некоторых случаях их можно обойти. Например, если веб-приложение имеет уязвимость межсайтового сценария (XSS), злоумышленник может использовать ее для выполнения сценария, который незаметно извлекает новую версию формы с текущим токеном CSRF. Чтобы предотвратить это и обеспечить надежную безопасность веб-приложений, убедитесь, что вы проверяете свое веб-приложение на все типы уязвимостей, а не только на CSRF.