Ответ
Это отличный вопрос, который затрагивает и саму уязвимость, и способ защиты.
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-токена:
- Уникальность: Должен быть уникальным для каждой сессии пользователя.
- Непредсказуемость: Генерируется с помощью криптографически стойкого генератора случайных чисел.
- Ограниченное время жизни: Токен должен устаревать вместе с сессией или через определенное время.
Ответ 18+ 🔞
А, ну это же классика, блядь! Просто пиздец, как часто эту хуйню спрашивают. Сейчас разжуем, как для дебила, но с технической точностью, ёпта.
Представь себе, сидишь ты в своём интернет-банке, типа mybank.com, авторизованный, как миленький. Куки твои, сессия — всё в ажуре. И тут ты, такой довольный, открываешь в другой вкладке сайт evil.com, где тебе показывают котиков или обещают увеличить член на 15 сантиметров за неделю.
А на этом сайте, сука, подлость! Там невидимая форма или даже просто картинка, которая, как только страница загрузилась, пиздует запрос на mybank.com/transfer?to=attacker&amount=1000. И браузер твой, тупая мартышлюшка, думая, что это ты сам захотел, отправляет этот запрос вместе со всеми твоими куками от банка. Банк смотрит: «А, это же наш Вася! Куки в порядке. Переводим тысячу на счёт злоумышленника». И ты остаёшься без бабла, даже не поняв, что произошло. Вот это и есть CSRF — межсайтовая подделка запроса. Тебя, по сути, подставили, используя твой же авторизованный браузер как оружие против тебя.
И как от этого спастись, блядь?
А вот тут на сцену выходит наш герой — CSRF-токен. Это такая хитрая, сука, заглушка, которую сервер вставляет в каждую форму, которую ты видишь.
Смысл в чём:
- Сервер, когда отдаёт тебе страницу с формой перевода, генерирует уникальный, криптостойкий, ебаный секретный код. И засовывает его в форму как скрытое поле.
- Ты жмёшь «отправить», и этот токен летит на сервер вместе с остальными данными.
- Сервер проверяет: «А токен-то тот ли, что я тебе дал?». Если совпал — всё ок, выполняем перевод. Если нет или его вообще нет — «иди нахуй, запрос поддельный».
В чём подвох для evil.com? Он не может этот токен ни украсть (из-за политики same-origin браузеров), ни угадать (токен криптостойкий). Поэтому его подлый запрос без токена будет послан нахуй банком. Защита, блядь!
Смотри, как это выглядит в коде на 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, который будет нахуй посылать все запросы без токена.
// Ключ должен быть длинным и секретным, а не как у некоторых — "12345".
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
http.ListenAndServe(":8080", csrfMiddleware(r))
}
func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
// 2. В шаблоне формы мы вставляем это волшебное поле. Оно само развернётся в `<input type="hidden" value="тут_длинный_токен">`.
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. Если запрос дошёл сюда — поздравляю, токен прошёл проверку в middleware.
// Можно спокойно обрабатывать. Если бы токен был невалидный, middleware вернул бы ошибку 403 Forbidden ещё до вызова этой функции.
w.Write([]byte("Форма успешно обработана!"))
}
И главное, блядь, помни про токен:
- Уникальный — для каждой сессии свой, а в идеале — для каждой формы.
- Непредсказуемый — чтобы его нельзя было подобрать или угадать. Генератор случайных чисел должен быть серьёзный, а не
rand.Intn(100). - Срок жизни — обычно привязан к сессии пользователя. Закрыл браузер — токен сдох. Или можно делать с таймаутом.
Вот и вся магия. Не такой уж и ёперный театр, правда? Главное — не забывать это хуйню использовать, а то потом удивляться, куда деньги с аккаунта деваются.