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 - коли продукт будується навколо спільного доступу з вкладеними групами й успадкуванням прав.
Головне - не модель, а дисципліна: усі перевірки в одному шарі (політики), кожна дія має правило, і є тести на заборону.
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, листи й логи.
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'], перевірка діапазонів для всіх числових полів; - явні стани й переходи для процесів (замовлення, оплата, верифікація) - перевірка поточного стану перед кожним кроком;
- атомарність операцій з балансами й лімітами (транзакції, блокування, унікальні обмеження);
- моделювання загроз для нових функцій: «як цим можна зловживати?» - разом з бізнесом.
Тести на зловживання («що буде, якщо кількість від'ємна», «що, якщо пропустити крок») корисні не менше, ніж тести щасливого шляху.
Адмін-панель дає доступ до даних усіх користувачів і до налаштувань системи - її компрометація зазвичай означає компрометацію всього застосунку. Тому захист будується в кілька шарів.
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: багатофакторна автентифікація