Что такое CSRF и как определить поддельный запрос
Что проверяют
Умеете ли вы на стороне сервера отличить запрос, который пользователь сделал осознанно, от запроса, который за него отправил чужой сайт. Для бэкендера на Go важна не теория, а место проверки в цепочке обработчиков и поведение API, у которого форм нет вовсе.
Как отвечать
Начать стоит с того, что куки и сессия признаком намерения не являются: браузер приложит их автоматически. Признаки подделки ищут так:
- токен-синхронизатор: случайное значение хранится в сессии и кладётся в скрытое поле формы, при разборе запроса сравнивается постоянным по времени сравнением (
subtle.ConstantTimeCompare); - без серверной сессии применяют двойную отправку: то же значение в куке и в поле или заголовке, но подписанное HMAC на серверном ключе, иначе поддомен сможет подсунуть свою куку;
- заголовок Origin, а при его отсутствии Referer, сверяется со своим хостом; если для изменяющего метода нет ни того ни другого, запрос отклоняется;
- для JSON API помогает требование собственного заголовка и типа содержимого: обычной формой такой запрос не отправить, а fetch уйдёт в предварительный запрос.
func sameOrigin(r *http.Request) bool {
o := r.Header.Get("Origin")
if o == "" {
o = r.Header.Get("Referer")
}
u, err := url.Parse(o)
return err == nil && u.Host == r.Host
}В проекте обычно берут готовое middleware вроде gorilla/csrf или nosurf. Оно оборачивает http.Handler целиком, ключ должен быть ровно тридцать два байта, а на локальном HTTP отдельно отключается требование защищённой куки.
Где спотыкаются
Ставят middleware и тут же дублируют проверку в обработчике, сравнивая токен из запроса с токеном, выданным этому же запросу. Такое сравнение истинно всегда, тест зелёный, защиты нет. Рядом обычное равенство строк вместо сравнения за постоянное время.
Называют CORS защитой от подделки. CORS решает, кто прочитает ответ; форма уйдёт на сервер, и изменение выполнится независимо от его настроек.
Закрывают только HTML-формы и забывают тот же метод в мобильном или внутреннем контуре. Сюда же изменяющие действия по GET: токен в адресе страницы утекает через Referer, историю браузера и логи прокси.