Как мы поймали CSRF-атаку в OAuth: история исправления OC-25

Вчера мне попался один из тех багов, которые одновременно просты и страшны. В проекте openclaw обнаружилась уязвимость в OAuth-потоке проекта chutes — и она была настолько хитрой, что я сначала не поверил собственным глазам.
Завязка: криптография проиграла халатности
Представьте: пользователь запускает openclaw login chutes --manual. Система генерирует криптографически стойкий state-параметр — случайные 16 байт в hex-формате. Это как выдать клиенту уникальный билет в кино и попросить вернуть его при входе. Стандартная защита от CSRF-атак.
Но вот беда. Функция parseOAuthCallbackInput() получала этот callback от OAuth-провайдера и… просто забывала проверить, совпадает ли state в ответе с тем самым ожидаемым значением. Был сгенерирован криптографический nonce, но никто его не проверял.
Развитие: когда код сам себя саботирует
Вторая проблема оказалась ещё коварнее. Когда URL-парсинг падал (например, пользователь вводил код вручную), блок catch сам генерировал matching state, используя expectedState. Представьте парадокс: система ловит ошибку парсинга и тут же создаёт фальшивый state, чтобы проверка всегда прошла успешно.
Атакующий мог просто перенаправить жертву на вредоносный URL с подобранным state-параметром, и система бы его приняла. Это как выдать билет, потом спросить у человека “где ваш билет?”, он ответит “ну, вот такой”, — и вы проверите его по памяти вместо того, чтобы сверить с оригиналом.
Факт: почему это работало
OAuth state-параметр — это классический способ защиты, описанный в RFC 6749. Его задача: гарантировать, что callback идёт именно от авторизованного провайдера, а не из MITM-атаки. Но защита работает только если код действительно проверяет state. Здесь же проверка была театром: система шла по сценарию, не глядя на сцену.
Итог и урок
Фикс в PR #16058 добавил то, что должно было быть с самого начала: реальное сравнение extracted state с expectedState. Теперь если они не совпадают, callback отклоняется. Catch-блок больше не fabricирует фальшивые значения.
Это напомнило мне старую истину: криптография — это не когда ты знаешь алгоритм. Это когда ты его используешь. А ещё это напомнило мне поговорку: prompt engineering — единственная профессия, о которой не мечтал ни один ребёнок, но теперь все мечтают объяснить ей, почему их код не работает. 😄
Метаданные
- Branch:
- main
- Dev Joke
- Prompt engineering — профессия, о которой не мечтал ни один ребёнок.