Головна / Статті

Чому Server Actions у Next.js потребують авторизації всередині кожного тіла функції

Детальний аналіз випадку захоплення облікового запису показує, як неперевірені Server Actions у Next.js дозволяють виконувати привілейовані операції, та де має знаходитися перевірка авторизації, щоб це запобігти.

Видавець: REACTAPP.TOP4 хв читання994 слів

Джерела: Unauthenticated Next.js Server Actions Leading to Zero Click Account Takeover

Server Actions у Next.js дозволяють компоненту React безпосередньо викликати функцію на сервері, без необхідності використання API-шляху. Ця зручність усуває єдине місце, де багато команд звикли розміщувати перевірку авторизації — обробник шляху. Коли перевірка не переміщується разом із логікою в тіло функції, її може викликати будь-хто, хто знає її ідентифікатор. У наведеному нижче прикладі детально показано саме таку проблему у реальному порталі підписок, де два незахищені дії були поєднані для миттєвого переходу контролю над обліковим записом, а потім описано захисні правила, які могли б це запобігти.

Як насправді відправляються Server Actions

У рамках App Router серверна дія — це функція сервера, яка викликається безпосередньо з компонента. Фреймворк присвоює кожній дії 40-значний шістнадцятковий ідентифікатор, додає цей ідентифікатор до клієнтського пакету та перетворює виклик на запит POST із заголовком Next-Action. Ключовою деталлю є те, що відправка сигналу здійснюється за допомогою цього заголовка, а не шляху URL, а тіло запиту — це просто аргументи функції, передані за порядком.

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

Перша дія: електронний лист надходить, привілейований запис виходить

У розглядуваному порталі запит, надісланий без кук, заголовка авторизації та будь-яких інших даних, які могли б ідентифікувати викликача, досяг першої дії. У тілі запиту були лише адреса електронної пошти та булеве значення.

POST /particuliers/integrale-web-mensuel-2116 HTTP/2
Host: subdomain.target.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Next-Action: 42a24b711d12f695d798b371e38b584c551ebb6a
Content-Type: text/plain;charset=UTF-8
Accept: text/x-component
["victim@gmail.com",true]

У відповіді була повернута повна інформація про обліковий запис, що належить цій електронній пошті.

{
"user_id": "auth0|000000000000000000000001",
"email": "researcher@example.com",
"identities": [
{ "provider": "auth0", "connection": "www-target-com" }
],
"app_metadata": {
"subscriber_number": "900000000",
"custom_user_id": "00000000–0000–4000–8000–000000000000"
}
}

Форма цього вантажу є ключовим показником. Поля на кшталт identities та app_metadata не надаються через публічний API автентифікації постачальника ідентичностей; вони походять з його API керування, адміністративного інтерфейсу, до якого доступ відбувається за допомогою токена між машинами з правами на читання та оновлення даних користувачів. Отримання цих даних через неперевірений запит означає, що на сервері знаходився привілейований токен, який тихо використовувався анонімною функцією. Окрім того, у відповіді також були передані два значення, необхідні для наступної дії: ідентифікатор користувача та назва з’єднання.

Наступна дія: встановлення пароля будь-якого користувача

Для наступної дії були прийняті об’єкт та рядок. Об’єкт містив пароль та інформацію про з’єднання; рядок — ідентифікатор користувача. При надсиланні цього запиту на тестовий обліковий запис, де все ще не було автентифікації, вигляд відповіді був таким.

POST / HTTP/2
Host: subdomain.target.com
Next-Action: 89abcdef0123456789abcdef0123456789abcdef
Content-Type: text/plain;charset=UTF-8
Accept: text/x-component
[{"password":"<REDACTED>","connection":"www-target-com"},"auth0|000000000000000000000001"]

Цей запит нічого не довів: не було жодного куки сесії, жодних знань про існуючий пароль, жодного токена для скидання чи підтвердження електронною поштою, а також жодного запиту на підтвердження. Сервер відповів про успіх та оновлений об’єкт користувача. Вирішальною помилкою є те, звідки береться ідентифікатор користувача: це другий аргумент, наданий викликаючим кодом, а не дані, отримані з сесії чи з поля subject у підписаному токені. Функція змінює той обліковий запис, про який їй сказано.

Та сама дія, інше поле, викрадений шлях відновлення

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

POST / HTTP/2
Host: subscribe.target.com
Next-Action: 89abcdef0123456789abcdef0123456789abcdef
Content-Type: text/plain;charset=UTF-8
Accept: text/x-component
[{"email":"attacker@example.com","connection":"www-target-com"},"auth0|000000000000000000000001"]

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

Уроки, які дає цей випадок

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