Перейти к содержимому
← Каталог golang CSRF

CSRF: Атака через JSON API без кастомных заголовков

JSON API эндпоинт не требует кастомных заголовков. Используй CSRF для смены пароля admin через API.

mediumgolangPro
Задача
# CSRF через JSON API без кастомных заголовков ## Сценарий Вы проводите аудит веб-приложения **SecureShop**. Приложение предоставляет JSON API для обновления профиля. В ходе ревью вы обнаружили эндпоинт `POST /api/profile` — он принимает JSON-тело с полями пользователя (включая `password`) и не требует никакой CSRF-защиты. Разработчик считает, что JSON API «по определению» безопасен от CSRF, потому что классические HTML-формы не отправляют JSON. Это широко распространённое заблуждение: браузерные API (`sendBeacon`, `fetch` в режиме `no-cors`, HTML-формы с `enctype=text/plain`) умеют обходить эту мнимую защиту. ## Цель 1. Убедитесь, что эндпоинт `POST /api/profile` действительно не проверяет CSRF-токен и не требует кастомных заголовков. 2. Сформируйте cross-origin payload, который отправит JSON-тело со сменой пароля для текущего залогиненного пользователя. 3. Доставьте payload в браузер с активной admin-сессией так, чтобы пароль администратора сменился на известное вам значение. 4. Войдите как `admin` с новым паролем и заберите флаг с `/admin`. ## Теория **CSRF на JSON API** — расширение классической CSRF-атаки на REST-эндпоинты, принимающие JSON. Многие разработчики ошибочно полагают, что если приложение принимает только `Content-Type: application/json`, то атака невозможна — ведь `<form>` отправляет либо `application/x-www-form-urlencoded`, либо `multipart/form-data`. Это заблуждение: если сервер парсит тело запроса как JSON независимо от заголовка `Content-Type` (например, через `json.Unmarshal(body, ...)`), браузер можно убедить отправить JSON через несколько лазеек: * `<form enctype="text/plain">` — особый формат отправки `name=value` пар, который при правильном подборе имён превращается в валидный JSON. * `navigator.sendBeacon()` с `Blob` — позволяет указать произвольный `Content-Type` (включая `text/plain`), при этом браузер прикрепляет cookies жертвы. * `fetch(..., {mode: 'no-cors'})` с `text/plain` — не вызывает CORS preflight, потому что считается «простым» запросом. Реальная защита JSON API от CSRF строится на одном из двух механизмов: проверка обязательного **кастомного HTTP-заголовка** (браузер не может добавить его в «простом» cross-origin запросе без CORS preflight, а HTML-формы и `sendBeacon` не умеют добавлять кастомные заголовки вообще), либо классический **CSRF-токен** в заголовке или теле. **Уязвимый паттерн:** ```go func (h *Handler) APIUpdateProfile(w http.ResponseWriter, r *http.Request) { user := h.getUser(r) if user == nil { /* 401 */ return } // Нет проверки источника, нет CSRF-токена, нет требования кастомного заголовка: body, _ := io.ReadAll(r.Body) var req ProfileUpdateRequest json.Unmarshal(body, &req) // парсит JSON независимо от Content-Type if req.Password != "" { hash, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) h.db.Exec("UPDATE users SET password=? WHERE id=?", hash, user.ID) } } ``` ## Точка входа атаки | Параметр | Значение | |----------|----------| | Ваш аккаунт | `demo` / `demo` | | Уязвимый эндпоинт | `POST /api/profile` | | Поле для смены | `password` (в JSON-теле) | | Доставка боту | `POST /report` (бот залогинен как admin) | | Цель | `GET /admin` (требует роль `admin`, показывает флаг) | | Exploit Server / hosting | внешний хостинг для HTML-payload (см. панель лабы → вкладка HTML Payload) |
🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru