Houdrik
Engineering
· 15 травня 2025 р.· 10 min read

Автентифікація, яка переживе перевірку за OWASP

AI-модель збере форму входу за 90 секунд, і виглядатиме вона правильно. Чого вона не зробить навіть із пристойною бібліотекою — не переживе серйозного аудиту безпеки. Ось список того, чого бракує.

Cover · auth-that-survives-an-owasp-pass

AI-модель збере робочу форму входу за 90 секунд. Вона обере пристойну бібліотеку. Вона захешує паролі правильним алгоритмом. Форма відрендериться, прийме облікові дані, виставить cookie й зробить перенаправлення кудись адекватно. Для неспеціаліста, який читає зміни, виглядатиме готовим.

Чого вона не зробить навіть із тією пристойною бібліотекою — не переживе серйозної перевірки безпеки. Список того, що системно відсутнє, короткий, конкретний і майже однаковий у кожній зібраній AI кодовій базі, яку ми аудитували за останній рік. Жодна з цих помилок не екзотична. Кожна — за один пошук у OWASP. Сумарно — це кілька днів роботи, і ніхто цю роботу не планує, поки щось не зламається.

1. Скидання сесії при зміні пароля

Коли користувач змінює пароль, кожна наявна сесія цього користувача має померти. Не лише поточна — кожна. Це найсистемніше пропущений контроль, який ми бачимо. За замовчуванням більшість фреймворків ротує CSRF-токен поточної сесії й на цьому зупиняється. Сесії на інших пристроях, в інших браузерах, на вкраденому ноутбуці, через який користувач саме і змінює пароль, — продовжують працювати.

Виправлення мале й повністю механічне. Зберігайте ціле число session_token_version у записі користувача. Включайте його в підписану cookie сесії. Збільшуйте його при зміні пароля, при зміні email, при явному "вийти з усіх пристроїв". Відхиляйте будь-яку сесію, у якої вбудована версія не збігається з поточною. От і все. Це зміна на півдня, яка закриває цілий клас атак, до яких прототип був повністю відкритий.

2. Обмеження частоти на вхід і скидання пароля з правильними значеннями за замовчуванням

Ендпойнт входу без обмеження частоти — це ціль для перебору вкрадених паролів із моменту, коли він з'являється у списку. Ендпойнт скидання пароля без обмеження частоти — це безкоштовна зброя для email-флуду, націлена на ваш платний поштовий сервіс. AI-каркас рідко містить хоч щось із цього, а коли містить — ліміти це довільні числа, обрані без огляду на те, що стоїть за ендпойнтом.

Цікаве питання — пропускати чи блокувати в разі збою самого обмежувача. Якщо лічильник недоступний — Redis впав, лічильник у пам'яті перезапустився, — ендпойнт приймає запит чи відмовляє? Правильне рішення залежить від того, що за ним стоїть. Для входу за зміцненим провайдером ідентичності з власним захистом від брутфорсу пропустити — розумний варіант. Для власного потоку скидання пароля, що шле листи через платного відправника, блокувати — єдиний безпечний вибір. Обирайте свідомо. Документуйте рішення поряд із обмежувачем.

3. Запобігання перебору email-адрес

Сценарії скидання пароля і реєстрації мають повертати однакову форму відповіді незалежно від того, чи існує email у базі. Той самий код статусу, те саме тіло, той самий час відповіді в межах розумної варіації. Усі кажуть, що так роблять. Майже ніхто реально цього не робить.

Типові ознаки: реєстрація каже "email уже зареєстровано"; скидання каже "ми надіслали вам посилання" лише коли акаунт існує, і "email не знайдено", коли ні; сценарії підтвердження зміни пароля показують різні помилки залежно від того, який крок не пройшов. Кожне з цього — безкоштовний оракул для перебору користувачів. Виправлення не хитре: повертати ту саму нейтральну відповідь у всіх випадках, надсилати лист асинхронно, щоб час відповіді не був витоком, і тримати гілку "ми щось надіслали чи ні" повністю на стороні сервера, де її не видно нападнику. Баг у тому, що інженер, який пише форму, думає: чіткіше повідомлення про помилку — це перемога для користувацького досвіду. Воно і є — для легітимного користувача. Воно ж і публікує ваш список клієнтів.

4. CSRF-токени, прив'язані до правильних операцій

За замовчуванням фреймворк каже: "вимагай CSRF-токен на кожному POST". Це водночас і занадто широко, і занадто вузько. Занадто широко, бо багато POST-запитів публічні, ідемпотентні або вже захищені іншими засобами, і перевірка токена лише додає тертя. Занадто вузько, бо операції, які насправді потребують захисту — зміна пароля, зміна email, рух коштів, призначення ролі, будь-яка зміна стану, що цікава нападнику, — також живуть на PATCH, PUT і DELETE. Налаштування проміжного шару не завжди покривають їх однаково.

Що це затягує: явно перелічити операції, що змінюють стан, оздобити кожен оброблювач і трактувати все, чого немає у списку, як свідоме рішення, а не недогляд. Поєднайте перевірку токена з SameSite=Lax на cookie сесії для простих випадків і SameSite=Strict — для чутливих. Перевірте будь-який ендпойнт, що приймає параметр redirect_to, і переконайтеся, що перевірка CSRF відбувається до перенаправлення, а не після. Це нудно й непривабно. Це також місце, де живе більшість реальних CSRF-вразливостей.

5. Модель прав — один раз, послідовно

Типовий патерн у зібраній AI кодовій базі — точкові перевірки на кожному маршруті. if user.is_admin or user.id == resource.owner_id, розкидані по тридцяти в'юшках. На перший день це працює. До шостого місяця гниє: одна в'юшка перевіряє is_admin, інша — is_staff, третя — groups.filter(name='admin').exists(), і різниця між ними не задокументована, бо ніхто не пам'ятає, чому написав саме так.

Правильна форма — єдиний шар прав, через який проходить кожна захищена операція. Назвіть це RBAC, ABAC, модулем політик чи просто функцією can(user, action, resource) — оберіть одне, запишіть, і зробіть точкові перевірки на маршрутах порушенням, яке рецензенти відхиляють у запитах на злиття. Ціна — тиждень консолідації на старті. Користь — наступний аудит безпеки знаходить відповідь на питання "хто може зробити X" читанням одного файлу замість пошуку по тридцяти варіаціях регулярних виразів.

6. Багатоорендна ізоляція на рівні бази, а не в коді застосунку

Це те, що старіє найгірше. Прототип забезпечує ізоляцію орендарів у ORM: кожен запит фільтрує за tenant_id. Рев'ю коду це ловить. Тести покривають. Потім хтось рефакторить, додає новий шлях запиту через Celery-завдання, забуває фільтр, і рахунки одного орендаря з'являються на дашборді іншого.

Є два захисти, які варто мати. Безпека на рівні рядків (RLS) у Postgres зсуває виконання у базу: з'єднання виставляє змінну сесії, політики на кожній таблиці вимагають, щоб ця змінна збігалася з tenant_id рядка, і запит без фільтра повертає нуль рядків замість даних чужого орендаря. Компроміс операційний: кожне з'єднання потребує коректно виставленої змінної, пулінг з'єднань стає делікатнішим, а налагодження "чому цей запит нічого не повертає" стає новим режимом збою. Альтернатива — обмежені запити з явним tenant_id на кожному виклику — тримає складність у коді застосунку, де про неї легше міркувати, але покладається на дисципліну, яка зникає в момент, коли в команду приходить хтось новий. Обирайте одне. Ми за замовчуванням ставимо RLS на все, де більше двох орендарів, бо операційна ціна обмежена, а ціна витоку даних — ні.

Робота, яку ніхто не планує

Кожен пункт — за один пошук у OWASP. Жоден не вимагає нових досліджень. Ретельний інженер зі списком пройде це за тиждень-два. Причина, чому це не робиться, — ніхто не планує цю роботу. Форма входу вже існує. Вона вже приймає логіни. Менеджер продукту хоче наступну функцію. Аудит безпеки залишається теоретичним рівно до того моменту, коли він перестає таким бути.

Ми робимо цю роботу, бо ми були тією черговою командою, коли аудит перестав бути теоретичним. У інженерного листа о 9 вечора в середу, коли зовнішній дослідник за один день знайшов три з шести пунктів вище, особливий тон, і це не той тон, який хочеться вчити вдруге.

Це не глянцева інженерія. Це робота, яка займає спринт, щоб зробити її правильно, і ніхто не святкує, коли вона випускається, бо нічого видимо не змінюється. Це також та підлога, нижче якої "промислова якість" не означає нічого змістовного. Якщо поверхня автентифікації не пережила реальну перевірку за OWASP, надійність решти системи — це декорація.

Маєте додаток, який має жити?

Виведіть його з прототипу в продакшн.

Відповідаємо протягом одного робочого дня. MVP, написаний на відчуттях, чернетка від AI, недороблений проєкт або робочий продукт, що починає тріщати — усе приймається.

Запустити проєкт