Что такое CSRF-атака и как CSRF-токен помогает от неё защититься?

Ответ

Это отличный вопрос, который затрагивает и саму уязвимость, и способ защиты.

1. Сначала об атаке: что такое CSRF?

CSRF (Cross-Site Request Forgery) или "Межсайтовая подделка запроса" — это тип атаки, при которой злоумышленник заставляет браузер аутентифицированного пользователя выполнить нежелательное действие на сайте, которому пользователь доверяет.

Пример:

  1. Вы залогинены в своем интернет-банке mybank.com.
  2. Вы открываете в новой вкладке вредоносный сайт evil.com.
  3. На этом сайте есть невидимая форма или картинка, которая автоматически отправляет запрос на mybank.com/transfer?to=attacker&amount=1000.
  4. Ваш браузер, имея валидные cookie для mybank.com, послушно отправит этот запрос вместе с ними. Банк посчитает запрос легитимным и выполнит перевод денег.

2. Как CSRF-токен решает эту проблему?

CSRF-токен — это уникальный, секретный и непредсказуемый код, который сервер генерирует для сессии пользователя и встраивает в веб-формы.

Механизм защиты:

  1. Когда пользователь открывает страницу с формой (например, для перевода денег), сервер генерирует токен и вставляет его в скрытое поле формы.
  2. Когда пользователь отправляет форму, этот токен уходит вместе с запросом.
  3. Сервер перед выполнением действия проверяет, совпадает ли токен из запроса с тем, что он сохранил для этой сессии.

Сайт злоумышленника (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-токена:

  • Уникальность: Должен быть уникальным для каждой сессии пользователя.
  • Непредсказуемость: Генерируется с помощью криптографически стойкого генератора случайных чисел.
  • Ограниченное время жизни: Токен должен устаревать вместе с сессией или через определенное время.