Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Middle: питання на співбесіді з теми «Контроль доступу»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

RBAC (Role-Based Access Control) - права видаються ролям, а ролі - користувачам.

editor  → posts.create, posts.update, posts.publish
viewer  → posts.view
Оля     → editor
  • просто пояснити, просто адмініструвати («зробити Іру редактором»);
  • у Laravel - spatie/laravel-permission, власні таблиці ролей і прав;
  • слабкість: погано виражає правила «залежно від об'єкта». «Редактор може редагувати лише статті свого відділу» - уже не чиста роль. Спроби втиснути це в RBAC породжують «вибух ролей» (editor-marketing, editor-sales, ...).

ABAC (Attribute-Based Access Control) - рішення на основі атрибутів користувача, ресурсу, дії й контексту:

public function update(User $user, Document $document): bool
{
    return $user->department_id === $document->department_id
        && $document->status !== 'archived'
        && $user->clearance >= $document->classification;
}
  • гнучко: відділ, статус документа, рівень доступу, час, IP, країна;
  • політики Laravel - це фактично ABAC у коді;
  • слабкість: правила розкидані в коді, їх важче перевірити й пояснити («чому Оля бачить цей документ?»).

ReBAC (Relationship-Based Access Control) - доступ визначається зв'язками між об'єктами:

Оля - учасник команди «Маркетинг»
«Маркетинг» - власник папки «Кампанії 2026»
Документ - у папці «Кампанії 2026»
⇒ Оля може переглядати документ
  • природно для спільного доступу: Google Drive, GitHub (організації, команди, репозиторії), Notion;
  • модель Google Zanzibar і її реалізації (OpenFGA, SpiceDB) масштабують такі перевірки на мільярди зв'язків;
  • слабкість: окрема інфраструктура, складність налагодження ланцюжків зв'язків.

На практиці - комбінація:

  • RBAC для грубих можливостей («хто має доступ до адмінки», «хто може запрошувати учасників»);
  • ABAC / перевірки в політиках для правил щодо конкретних об'єктів («автор може редагувати свою статтю, поки вона не опублікована»);
  • ReBAC - коли продукт будується навколо спільного доступу з вкладеними групами й успадкуванням прав.

Головне - не модель, а дисципліна: усі перевірки в одному шарі (політики), кожна дія має правило, і є тести на заборону.

Докладніше в документації: NIST: Role Based Access Control

IDOR (Insecure Direct Object Reference) - застосунок використовує ідентифікатор від клієнта, щоб знайти об'єкт, і не перевіряє, чи має користувач до нього доступ. Ендпойнти з {id} в адресі зазвичай перевіряють. Вразливості частіше ховаються в менш помітних місцях.

1. Ідентифікатори в тілі запиту й прихованих полях:

<input type="hidden" name="account_id" value="15">
Transfer::create(['from_account_id' => $request->account_id, ...]);   // рахунок не перевірено

Прихований чи disabled у формі - не захищений: його змінюють у DevTools.

2. Завантаження й перегляд файлів: /download?file=invoices/1041.pdf, /attachments/88/preview, прямі посилання на файли в публічному сховищі.

3. Експорт і звіти: /export?user_ids[]=5&user_ids[]=6 - перевіряють права на експорт загалом, але не на кожен переданий ідентифікатор.

4. Масові дії: «видалити обрані» отримує масив id - перевірка лише першого чи жодного.

5. Фільтри списків: /orders?customer_id=7 - список фільтрується за параметром, а не обмежується правами користувача.

6. Пов'язані об'єкти при створенні: POST /comments {"post_id": 42} - коментар до приватного поста, до якого немає доступу; {"team_id": 9} - додати себе в чужу команду.

7. Livewire й інші компоненти зі станом: ідентифікатор у публічній властивості, який змінюють у браузері (без #[Locked] чи моделі у властивості).

8. GraphQL і вкладені поля: доступ до об'єкта перевірено, а до вкладених зв'язків (user { orders { ... } }) - ні.

9. Адмінки й службові панелі: перевірено, що користувач - адміністратор, але не що він адміністратор цієї організації.

Як закрити системно:

  • шукати через власника чи область видимості: $user->accounts()->findOrFail($id), $team->members()->...;
  • валідація з умовою власності: Rule::exists('accounts', 'id')->where('user_id', $user->id);
  • політика для кожного об'єкта, отриманого з ідентифікатора, - включно з масивами (foreach ($ids ...) чи один запит з умовою власності);
  • глобальні області видимості для багатоорендних даних - щоб чужі записи не потрапляли в запити за замовчуванням.

Непередбачувані ідентифікатори (UUID) ускладнюють перебір, але не замінюють перевірку: ідентифікатори «протікають» через URL, листи й логи.

Докладніше в документації: OWASP: запобігання IDOR

Gate::before реєструє колбек, який виконується перед усіма перевірками гейтів і політик. Поширений прийом - суперадміністратор, якому дозволено все:

Gate::before(function (User $user, string $ability) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

Як працює результат:

  • true - дозвіл, жодні гейти й політики далі не викликаються;
  • false - заборона, теж без подальших перевірок;
  • null (нічого не повертати) - звичайна перевірка продовжується.

Пастки:

1. return false замість «нічого».

Gate::before(fn (User $user) => $user->isSuperAdmin());   // помилка!

Для звичайного користувача стрілкова функція повертає false - і всі перевірки в застосунку стають забороненими. Колбек має повертати true або null.

2. Суперадмін може «все» - справді все. true з before дозволяє і дії, які мали б бути заборонені для всіх за бізнес-правилами: видалити оплачене замовлення, змінити закритий фінансовий період, редагувати документ, підписаний іншою стороною. Якщо такі обмеження є, їх варто перевіряти поза гейтами (у сервісі, доменному об'єкті) або виключати в before:

Gate::before(function (User $user, string $ability) {
    if ($user->isSuperAdmin() && ! in_array($ability, ['delete-paid-order', 'impersonate'], true)) {
        return true;
    }
});

3. Ризик облікового запису. Обліковий запис суперадміністратора - найцінніша мішень: обов'язкова двофакторна автентифікація, мінімум таких користувачів, журнал усіх дій.

4. Ознака «суперадміністратора» має бути надійною: поле в базі, яке не змінюється через масове призначення, а не email чи ім'я, що їх можна підробити в іншому контексті.

before у політиці - те саме, але лише для однієї моделі:

class PostPolicy
{
    public function before(User $user, string $ability): ?bool
    {
        return $user->is_moderator ? true : null;
    }
}

Особливість: метод before політики не викликається, якщо в політиці немає методу з назвою перевірюваної дії.

Gate::after - виконується після перевірки; його результат враховується, лише якщо основна перевірка повернула null. Зручно для аудиту рішень.

Тест, який варто мати: звичайний користувач отримує дозвіл на свої дії (захист від пастки з false) і відмову на чужі.

Докладніше в документації: Laravel: перехоплення перевірок гейтів

Вразливості бізнес-логіки - кожен запит технічно коректний (правильні типи, валідний токен, без ін'єкцій), але послідовність чи комбінація дій дає результат, якого бізнес не передбачав. Автоматичні сканери шукають відомі шаблони (XSS, SQLi) і не знають правил вашого бізнесу - тому такі помилки знаходять люди.

Типові приклади:

1. Від'ємні й граничні значення:

POST /cart/items {"product_id": 5, "quantity": -3}

Від'ємна кількість зменшує суму замовлення чи повертає гроші на баланс. Те саме з нульовими цінами, дробовими кількостями, величезними числами (переповнення).

2. Довіра даним від клієнта: ціна, знижка чи сума доставки приходять у запиті й використовуються як є, замість обчислення на сервері.

3. Пропуск кроків процесу: оформлення замовлення «адреса → оплата → підтвердження». Запит відразу на крок підтвердження - і замовлення створено без оплати. Сервер має перевіряти стан процесу, а не вірити, що клієнт пройшов попередні кроки.

4. Купони й акції:

  • повторне застосування одного купона;
  • застосування після зміни кошика (купон «від 1000 грн» лишається після видалення товарів);
  • поєднання несумісних знижок;
  • реферальні бонуси за самого себе через другий обліковий запис.

5. Гонитва запитів: десять паралельних запитів «застосувати купон» чи «вивести кошти» проходять перевірку одночасно, до того як перший запише результат.

6. Зміна даних після перевірки: спершу підтвердили email, потім змінили на інший - і він вважається підтвердженим.

7. Обхід обмежень через альтернативний шлях: обмеження ліміту в інтерфейсі, але не в API; перевірка у веб-версії, але не в мобільному ендпойнті.

Як захищатися:

  • сервер обчислює все важливе: ціни, знижки, суми, доставку - з бази, а не з запиту;
  • валідація меж: 'quantity' => ['integer', 'min:1', 'max:100'], перевірка діапазонів для всіх числових полів;
  • явні стани й переходи для процесів (замовлення, оплата, верифікація) - перевірка поточного стану перед кожним кроком;
  • атомарність операцій з балансами й лімітами (транзакції, блокування, унікальні обмеження);
  • моделювання загроз для нових функцій: «як цим можна зловживати?» - разом з бізнесом.

Тести на зловживання («що буде, якщо кількість від'ємна», «що, якщо пропустити крок») корисні не менше, ніж тести щасливого шляху.

Докладніше в документації: PortSwigger: логічні вразливості

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

1. Сильна автентифікація:

  • обов'язкова двофакторна автентифікація для всіх облікових записів з доступом до адмінки - краще ключі безпеки чи passkeys (стійкі до фішингу), ніж SMS;
  • окремі облікові записи для кожної людини - без спільних «admin@company»;
  • коротша сесія й повторне підтвердження пароля перед критичними діями (Laravel - middleware password.confirm).

2. Мінімальні права:

  • ролі в адмінці (підтримка, модератор, фінанси, суперадміністратор), а не «адмін може все»;
  • політики на кожен ресурс і дію - як і в основному застосунку; Filament, Nova та подібні використовують політики Laravel;
  • суперадміністраторів - мінімум.

3. Обмеження доступу на рівні мережі:

  • окремий піддомен (admin.example.com) - простіше обмежити й моніторити;
  • дозволений список IP чи доступ через VPN / Zero Trust (Cloudflare Access тощо) - адмінка невидима з інтернету;
  • нестандартний шлях не є захистом - лише зменшує шум від сканерів.

4. Захист від типових атак:

  • обмеження частоти спроб входу й блокування після серії невдач;
  • CSRF-захист і frame-ancestors 'none' (адмінки - улюблена мішень clickjacking);
  • суворий CSP - XSS в адмінці небезпечніший, ніж на публічних сторінках.

5. Журнал дій:

  • хто, коли, що змінив, з якої IP - для всіх змін у адмінці;
  • особливо - зміни ролей, прав, налаштувань безпеки, експорти даних;
  • журнал недоступний для зміни самими адміністраторами;
  • сповіщення про аномалії (масові експорти, вхід з нової країни).

6. Обережно з «увійти як користувач» (impersonation): дуже зручно для підтримки, але це повний доступ до облікового запису. Лише для обмеженої ролі, з журналом, явною позначкою в інтерфейсі й заборонами (не змінювати пароль, не робити платежі від імені користувача).

7. Оновлення: пакети адмінок (Filament, Nova, сторонні) - така сама частина ланцюжка постачання; оновлювати й стежити за бюлетенями безпеки.

Регулярна ревізія доступів: хто має доступ до адмінки зараз, і чи досі він потрібен (звільнені співробітники, підрядники, тестові облікові записи).

Докладніше в документації: OWASP: багатофакторна автентифікація