CSRF: SameSite=Lax Bypass через GET-запрос
Мутирующий эндпоинт смены email реализован через GET. SameSite=Lax не защищает от top-level навигации. Измени email администратора через CSRF.
mediumgolangPro
Задача
# CSRF: SameSite=Lax Bypass через GET-эндпоинт
## Сценарий
Вы проводите аудит безопасности интернет-магазина **SecureShop**. Разработчик добавил быстрый эндпоинт смены email через GET-запрос: `GET /api/email/change?email=...` — «удобный для CLI и тестирования». Команда уверена, что приложение защищено от CSRF, потому что cookie сессии выставлена с атрибутом `SameSite=Lax`. Это распространённое заблуждение: `Lax` блокирует только cross-site POST/PUT/DELETE, но **разрешает cookies при top-level GET-навигации** — переход по ссылке, редирект, `window.location.href = ...`.
## Цель
1. Разберитесь, почему `SameSite=Lax` не защищает GET-эндпоинты, выполняющие мутации (изменение состояния).
2. Подготовьте на exploit-server HTML-страницу с автоматическим переходом на уязвимый GET-URL (через `<meta refresh>`, `window.location` или просто `<a href>` + автоклик).
3. Через форму `/report` отправьте ссылку боту-администратору. Бот откроет страницу — браузер выполнит top-level GET-навигацию на `GET /api/email/change?email=...` со своими admin-cookie.
4. Email администратора изменится — после этого **флаг появится в каталоге** `/catalog`, в карточке товара «CTF Flag» (сервер подставляет его, обнаружив смену email администратора).
## Теория
**`SameSite` cookie attribute** управляет тем, прикрепляются ли cookies к cross-site запросам. Возможные значения:
* `None` — cookies прикрепляются всегда (требует `Secure`).
* `Lax` (значение по умолчанию во многих современных браузерах) — cookies прикрепляются к **top-level GET-навигации** (переход по ссылке, редирект, `<form method=GET>`). Cross-site POST/PUT/DELETE/AJAX отбрасываются.
* `Strict` — cookies прикрепляются **только** если запрос инициирован с того же origin. Полностью блокирует CSRF, но ломает UX: переход на сайт по внешней ссылке выглядит как «вы не залогинены».
Главная ошибка в этой лабе — сочетание двух решений, по отдельности нормальных:
1. Уязвимый эндпоинт принимает GET-запрос с побочным эффектом (изменение email).
2. Cookie выставлена с `SameSite=Lax`.
Lax не блокирует top-level GET, поэтому атакующий может заставить браузер жертвы перейти по `https://target/api/email/change?email=attacker@evil.com` любым способом — `<a href>` + автоклик, `<meta http-equiv="refresh">`, `window.location = ...`. Cookie уйдёт на сервер вместе с запросом, эндпоинт изменит email.
**Уязвимый паттерн (двойная ошибка):**
```go
// (1) Маршрут смены email привязан к GET — мутация через идемпотентный метод:
mux.HandleFunc("GET /api/email/change", h.ChangeEmail)
// (2) Cookie выставлена с SameSite=Lax — Lax разрешает top-level GET:
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: token,
Path: "/",
SameSite: http.SameSiteLaxMode, // Lax не защищает GET-мутацию
HttpOnly: true,
})
func (h *Handler) ChangeEmail(w http.ResponseWriter, r *http.Request) {
user := h.getUser(r)
if user == nil { /* 401 */ return }
newEmail := r.URL.Query().Get("email") // GET-параметр
h.db.Exec("UPDATE users SET email=? WHERE id=?", newEmail, user.ID)
}
```
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Ваш аккаунт | `demo` / `demo` |
| Уязвимый эндпоинт | `GET /api/email/change?email=...` |
| Доставка боту | `POST /report` |
| Где появится флаг | `GET /catalog` — карточка «CTF Flag» (после успешной CSRF) |
| Exploit Server / hosting | внешний хостинг для HTML-payload (см. панель лабы) |