После того как наигрались с Revel и поняли что это за чудик, пора учиться готовить его к production в части безопасности. Данная заметка вполне может использоваться в любом веб-приложении, но рассказывать буду на примере простого приложения на Revel.
Вообщем суть проблемы заключается в том, что существует класс атак в Интернете именуемые Cross-Site Request Forgery (сокр. CSRF). Если вы не знакомы с ними, то просьба обратить свое внимание. Приведу небольшую выдержку из Вики:
CSRF — вид атак на посетителей веб-сайтов, использующий недостатки протокола HTTP. Если жертва заходит на сайт, созданный злоумышленником, от её лица тайно отправляется запрос на другой сервер (например, на сервер платёжной системы), осуществляющий некую вредоносную операцию (например, перевод денег на счёт злоумышленника). Для осуществления данной атаки жертва должна быть аутентифицирована на том сервере, на который отправляется запрос, и этот запрос не должен требовать какого-либо подтверждения со стороны пользователя, который не может быть проигнорирован или подделан атакующим скриптом.
рис. Пример CSRF-атаки на сайт банка
Рекомендую также прочитать типичные ошибки при защите сайтов от CSRF-атак.
На свежем приложении Revel CSRF-атаки успешно проходят, т.к. защиты изначально нет.
Итак, начнем кутить с сусликом — установка защиты
Товарищи из Revel предлагают нам уже готовый модуль для защиты github.com/revel/modules/tree/master/csrf/app с инструкцией по установке:
CsrfFilter enables CSRF request token creation and verification.
Usage:
1) Add `csrf.CsrfFilter` to the app's filters (it must come after the revel.SessionFilter).
2) Add CSRF fields to a form with the template tag `{{ csrftoken. }}`. The filter adds a function closure to the `RenderArgs` that can pull out the secret and make the token as-needed, caching the value in the request. Ajax support provided through the `X-CSRFToken` header.
Собственно все ок! Добавил фильтр в init.go приложения по этой инструкции… И оно не заработает, если твой браузер не посылает в заголовке Referer. Поэтому пропустим всю установку этого фильтра в этой заметке и перейдем к альтернативному варианту.
Один из вариантов, предложенный в баг-трекере Revel на тему CSRF был проект: github.com/cbonello/revel-csrf. Принцип установки прост:
Сначала выкачиваем все к себе в GOPATH
go get github.com/cbonello/revel-csrf
Дальше в /app/init.go добавляем строчку в import секцию:
import (
"github.com/cbonello/revel-csrf"
"github.com/revel/revel"
)
А в функции init() в том же файле /app/init.go добавляем этот фильтр:
func init() {
// Filters is the default set of global filters.
revel.Filters = []revel.Filter{
...
revel.SessionFilter, // Restore and write the session cookie.
revel.FlashFilter, // Restore and write the flash cookie.
HeaderFilter, // Add some security based headers
csrf.CSRFFilter, // CSRF prevention. <----------------------------
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
...
}
...
}
Установка почти готова! Теперь все методы кроме GET, HEAD, OPTIONS, TRACE, WS считаются «небезопасными» и при отсутствии сгенерированного токена в запросе будет 403 ошибка.
Как передавать токен
Основные небезопасные запросы формируются с помощью веб-форм. Соответственно логчино теперь в каждую форму вставить скрытое поле, содержащий токен:
<form id="login-form" class="form-vertical" action="{{url "App.ConfirmLogin"}}" method="post">
<input type="hidden" name="csrf_token" value="{{.csrf_token }}">
...
</form>
Для упрощения можно взять функцию для шаблонизатора из модуля от команды Revel.
Большая часть действий так и решается — встраиванием скрытого поля в веб-формы. Но есть категория действий, которые не требуют отправки веб-формы, но являются небезопасными с точки зрения бизнес-логики. Например: выход пользователя по ссылке /logout. Провести атаку по такому действию очень просто, поэтому обработку такого действия необходимо перевести с безопасного GET метода на POST метод, который для нас является небезопасным. Для этого в файле /conf/routes добавим наше действие только для метода POST:
POST /logout App.Logout
Теперь необходимо сделать ссылки пригодными для работы с POST методами. Для таких случаев, можно использовать хорошую обертку для работы с ajax в RoR: github.com/rails/jquery-ujs
Устанавливаем к себе в проект через Bower:
$ bower install jquery-ujs --save
И подключим его к себе в шаблон. Писать подробности тут не буду как я делаю, у меня сборка через Grunt и подключение уже собранных скриптов. Надеюсь у тебя это труда не составит ;)
После подключения необходимо добавить еще пару строк в шапку, чтобы библиотека поняла какой токен отправлять:
<!DOCTYPE html>
<html lang="ru">
<head>
...
<meta name="csrf-token" content="{{.csrf_token}}">
<meta name="csrf-param" content="csrf_token">
...
</head>
...
После этого стоит включить еще поддержку Ajax у фильтра (по умолчанию выключена), для этого в файл /conf/app.conf нужно добавить следующее:
csrf.ajax = true
csrf.token.length = 64
Вторая строчка тут изменяет длину токена, по умолчанию она всего 32 символа. Длина может быть в диапазоне от 32 до 512 символов.
Если все подключено верно, то теперь можно делать такие безопасные ссылки, которые будут делать запрос POST методом:
<a href="{{url "App.Logout"}}" data-method="post">Выход</a>
На этом у меня всё. Спасибо за внимание!