Ответ
Это отличный вопрос, который затрагивает и саму уязвимость, и способ защиты.
1. Сначала об атаке: что такое CSRF?
CSRF (Cross-Site Request Forgery) или "Межсайтовая подделка запроса" — это тип атаки, при которой злоумышленник заставляет браузер аутентифицированного пользователя выполнить нежелательное действие на сайте, которому пользователь доверяет.
Пример:
- Вы залогинены в своем интернет-банке
mybank.com
. - Вы открываете в новой вкладке вредоносный сайт
evil.com
. - На этом сайте есть невидимая форма или картинка, которая автоматически отправляет запрос на
mybank.com/transfer?to=attacker&amount=1000
. - Ваш браузер, имея валидные cookie для
mybank.com
, послушно отправит этот запрос вместе с ними. Банк посчитает запрос легитимным и выполнит перевод денег.
2. Как CSRF-токен решает эту проблему?
CSRF-токен — это уникальный, секретный и непредсказуемый код, который сервер генерирует для сессии пользователя и встраивает в веб-формы.
Механизм защиты:
- Когда пользователь открывает страницу с формой (например, для перевода денег), сервер генерирует токен и вставляет его в скрытое поле формы.
- Когда пользователь отправляет форму, этот токен уходит вместе с запросом.
- Сервер перед выполнением действия проверяет, совпадает ли токен из запроса с тем, что он сохранил для этой сессии.
Сайт злоумышленника (evil.com
) не может узнать или угадать этот секретный токен, поэтому его поддельный запрос будет отклонен сервером из-за отсутствия или неверности токена.
Пример в Go с использованием gorilla/csrf
:
package main
import (
"html/template"
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/signup", ShowSignupForm)
r.HandleFunc("/process", ProcessForm).Methods("POST")
// 1. Создаем middleware для защиты
// Ключ должен быть секретным и храниться в конфигурации
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
http.ListenAndServe(":8080", csrfMiddleware(r))
}
func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
// 2. Вставляем токен в шаблон с помощью csrf.TemplateField
t, _ := template.New("signup.html").Parse(`
<form action="/process" method="POST">
<input type="text" name="name">
{{ .CSRFField }}
<button type="submit">Отправить</button>
</form>
`)
t.Execute(w, map[string]interface{}{
"CSRFField": csrf.TemplateField(r),
})
}
func ProcessForm(w http.ResponseWriter, r *http.Request) {
// 3. Если запрос дошел сюда, значит CSRF-токен валиден.
// Middleware уже выполнил проверку.
w.Write([]byte("Форма успешно обработана!"))
}
Ключевые характеристики хорошего CSRF-токена:
- Уникальность: Должен быть уникальным для каждой сессии пользователя.
- Непредсказуемость: Генерируется с помощью криптографически стойкого генератора случайных чисел.
- Ограниченное время жизни: Токен должен устаревать вместе с сессией или через определенное время.