Авторизація та контроль доступу
Зміст
Кнопки «Видалити проєкт» у читача в інтерфейсі немає — і на цьому перевірку прав закривають. Але інтерфейс це лише малюнок поверх запитів: той самий DELETE /api/projects/1 летить і повз браузер, тож питання рівно одне — чи стоїть перевірка там, куди він приходить. Найдорожчі знахідки цієї теми виглядають не як помилка, а як успіх: 200 OK, валідне тіло, зелений тест — і чужі дані на екрані.
Ця глава — канонічний виклад атакувального кута й означень ескалацій на сайті. Механіку перевірок у коді тут не переказуємо — вона в главі «Авторизація в API»; межу «хто ти» проти «що тобі можна» — у web-главі «Автентифікація та авторизація».
Ризик №1: що саме ламається
Контроль доступу (access control) — це виконання політики, за якою користувач не може діяти поза межами наданих йому дозволів; збій зазвичай веде до неавторизованого розкриття, зміни чи знищення даних або до виконання чужої бізнес-функції. Авторизація (authorization) означена так: «процес перевірки того, що запитану дію чи сервіс схвалено для конкретної сутності» (означення NIST, наведене OWASP). Дві межі варто знати в обидва боки: автентифікований користувач не має права на все, що система технічно вміє; і навпаки — неавтентифікований цілком може бути авторизований на публічні ресурси.
У редакції :2025 (Broken Access Control) лишається номер один, і цифра поруч — про частку застосунків, а не інцидентів: 100% протестованих застосунків мали ту чи іншу форму зламаного контролю доступу. Категорія має найбільше входжень у зібраних даних і другу за величиною кількість повʼязаних .
Межі категорії не інтуїтивні. Провідними названо CWE-200 і CWE-201 (розкриття чутливої інформації), CWE-352 (CSRF) і CWE-918 () — тобто CSRF і SSRF у редакції 2025 живуть усередині контролю доступу, і окремої позиції SSRF більше не має. Чому саме тут, видно із запису каталогу: підставивши URL на несподіваний хост, шле запит ніби руками сервера й обходить контроль доступу на кшталт файрвола, який не пускає його туди напряму. Механіка самої — тема глави про SSRF.
Типові дефекти категорії: порушення принципу (least privileges), коли доступ, розрахований на конкретні ролі, відкритий будь-кому; обхід перевірок правкою URL, внутрішнього стану застосунку чи HTML-сторінки або інструментом, який змінює API-запити; і — «дозвіл переглядати чи редагувати чужий акаунт, надавши його унікальний ідентифікатор». Серед сценаріїв атаки джерело наводить два, які прямо про це: , коли атакувальник просто відкриває цільову адресу, бо прав адміністратора для неї ніхто не перевіряє, — і застосунок, який «кладе весь свій контроль доступу у фронтенд», після чого береться прямим запитом повз браузер. Канонічно forced browsing означено як перебір і доступ до ресурсів, на які застосунок не посилається, але які лишаються досяжними; успіх приписано поганій реалізації механізму авторизації.
«Перевіряємо майже всюди» дефект не закриває: пропущена одна-єдина перевірка вже ставить під конфіденційність або цілісність ресурсу. Означень ескалацій категорія не дає — канонічне розведення двох осей живе в процедурі -ATHZ-03, з якої починається наступний підрозділ. Карта ризиків цілком — глава «Безпека очима QA».
Дві осі ескалації: вертикальна і горизонтальна
(privilege escalation) — доступ до більшого обсягу ресурсів чи функцій, ніж користувачеві належить, який застосунок мав був заблокувати; причина зазвичай у дефекті самого застосунку. Ступінь ескалації міряється різницею між тим, що атакувальник мав право мати, і тим, що здобув.
| Вісь | Рівень прав жертви | Приклад із джерела | Що потрібно для тесту |
|---|---|---|---|
| Вертикальна (vertical escalation) | вищий за наш | адміністративні права в застосунку | звичайний і привілейований акаунти |
| Горизонтальна (horizontal escalation) | такий самий | чужі дані в онлайн-банку | двоє рівних користувачів |
Вісь визначається рівнем прав акаунта-жертви, а не тим, що саме підмінили: той самий прийом дає проти рівного акаунта й вертикальну, коли підмінений ключ виявляється прапорцем адміністратора. Горизонтальну вісь канонічні джерела називають трьома різними назвами — WSTG каже horizontal escalation, шпаргалка OWASP — horizontal privilege elevation, MITRE — Horizontal Authorization; спільне в усіх трьох одне: однаковий рівень прав у двох сторін.
Вектори з процедури WSTG
Мета тесту сформульована прямо: переконатися, що користувач не може змінити свої привілеї чи ролі всередині застосунку. Базова техніка — повторити функцію під іншим користувачем. Чотири вектори з тих, що названі поіменно:
- група й обʼєкт — підміна параметрів
groupIDіorderIDкористувачем, який до тієї групи не належить; - профіль — приховане поле форми зі значенням профілю; питання джерела дослівне: «What if the tester modifies the value of the variable profile to SysAdmin?»;
- IP-адреса — якщо застосунок вважає
X-Forwarded-Forадресою клієнта, підміна заголовка обходить обмеження за IP; - обхід сайту — пройтися сторінками й пошукати ті, на яких перевірки авторизації просто немає.
Окремо стоїть зауваження : перевірка авторизації за частковим збігом URL обходиться кодуванням адреси. Причину вертикального вектора в API названо теж — складні політики з ієрархіями, групами й ролями та нечітке розділення адміністративних і звичайних функцій.
Поле, що несе привілей
Приховане поле форми — не деталь верстки, а повноцінний вхід негативного сценарію. Керований користувачем ключ живе у трьох місцях: приховане поле HTML-форми, параметр URL або незашифрована cookie. Коли такий ключ фактично є прапорцем адміністративного статусу, підміна дає вже , і тут інший, ніж у звичайній перевірці поля: джерело питає не «чи прийняли поле», а «чи вдалося стати адміністратором».
У словнику API це рівень властивостей обʼєкта: API3:2023 зі спільною першопричиною «брак або неправильна валідація авторизації на рівні властивості обʼєкта» — на відміну від BOLA, де порушення на цілому обʼєкті. У веб-редакції 2025 mass assignment стоїть в іншій категорії — A08 Software or Data Integrity Failures, поіменно як CWE-915. Процедура перевірки таких полів — тема розділу про API.
IDOR: чужий обʼєкт за ідентифікатором
IDOR (Insecure Direct Object Reference) — , коли атакувальник дістає або змінює обʼєкти, маніпулюючи ідентифікаторами в URL чи параметрах застосунку; причина названа прямо — відсутні перевірки доступу, які мали б зʼясувати, чи має користувач право на ці дані. Складників рівно три: обʼєкт (акаунт, документ, тікет, ), посилання на нього (ID, UUID, номер рахунку, токен або slug) і відсутня перевірка авторизації на рівні обʼєкта. Канонічний приклад в одну стрічку: /users/123 міняється на /users/124 — якщо показалися чужі дані, застосунок вразливий.
Мовою OWASP API Security Top 10 та сама вада зветься BOLA (Broken Object Level Authorization) і очолює список як API1:2023. Формулювання там точніше: доступ до самого ендпоінта законний за задумом, а порушення відбувається на рівні обʼєкта через маніпуляцію ідентифікатором. Поширена вона тому, що сервер не тримає стан клієнта й покладається на ідентифікатори від нього. Наслідки не зводяться до читання чужого: розкриття, втрата й зміна даних, іноді повне захоплення акаунта.
Ідентифікатор може бути числом, UUID або довільним рядком — і незалежно від типу його легко знайти в шляху, query, заголовках або тілі. Складний ідентифікатор захистом не є: GUID робить підбір практично неможливим, але перевірки лишаються обовʼязковими, а витеклу адресу застосунок зобовʼязаний заблокувати. Випадкові id — додатковий шар (defense-in-depth), а не заміна авторизації.
Три назви поруч — і жодне джерело не ставить між ними знак рівності. Клас «підміна ключа» має точний ідентифікатор — CWE-639 Authorization Bypass Through User-Controlled Key: авторизація не заважає одному користувачеві дістати дані іншого підміною ключа, яким ці дані ідентифікуються. Межі проведено явно: IDOR ширший за CWE-639, бо покриває ще й (CWE-22), а BOLA з ним лише частково перетинається. Ланку тримає шпаргалка OWASP: керований користувачем ключ пошуку — це CWE-639, і сам цей клас є формою IDOR.
Поруч живе межа з BFLA (Broken Function Level Authorization): якщо атакувальник дістався до ендпоінта, до якого не мав доступу, — це вже вона. Головне тут — не злити два різні розрізи в один. BOLA і BFLA розводяться за рівнем, на якому зламалася перевірка (обʼєкт проти функції), а ескалації — за рівнем прав акаунта-жертви. Спрощення «BOLA — це горизонталь, BFLA — вертикаль» виглядає акуратним і саме тому небезпечне: воно ховає найдорожчий випадок — поле-, яке з горизонтального вектора робить вертикальний.
Процедура пошуку теж не вигадана: завести User A і User B з різними обсягами прав, автентифікуватися як A, лізти в обʼєкти B через підміну посилань у запитах і чекати незалежно від того, вгадуваний ідентифікатор чи ні. Робити це для всіх операцій із посиланням на обʼєкт — читання, створення, оновлення, видалення, експорт і адміністративних дій. Порівняння id із сесії з параметром розвʼязанням не є, бо закриває малу підмножину випадків; радикальніше — не виставляти ідентифікатор у URL і тілі POST узагалі, а поточного користувача брати з інформації сесії. Ту саму механіку веб-редакція Top 10:2025 тримає серед типових дефектів A01, а готові перевірки з кодом — глава «Авторизація в API».
Forced browsing: чого немає в меню, але воно є
Означення вже прозвучало вище: перебір і доступ до ресурсів, на які застосунок не посилається, але які лишаються досяжними. Ключове слово тут — «не посилається»: відсутність лінка в інтерфейсі не робить ресурс недосяжним, і саме на цьому розриві живе атака. Те саме явище зустрічається під назвами Predictable Resource Location, File Enumeration, Directory Enumeration і Resource Enumeration.
Механіка — адрес: атакувальник шукає незалінкований вміст у каталозі домену, і джерело одразу називає, що саме, — тимчасові теки, файли, старі бекапи, конфігураційні файли. Цінність знахідки теж названа: вихідний код, облікові дані, внутрішня адресація мережі. Режимів два — вручну, коли адреси будуються на номерах чи передбачуваних значеннях, або автоматичним інструментом зі словником типових імен файлів і тек. Приклад передбачуваної адреси з джерела вартий памʼяті: у шляху видно логін і дату у форматі yyyymmdd, тож чужий календар відкривається підбором цих двох частин, без жодного словника.
Оракул простий і зручний для чекліста: відповідь 200 на адресу, якої в застосунку «немає», — це знахідка, яку далі оглядають руками. А от куди дивитися після неї, джерело каже прямо: «A bad implementation of the authorization mechanism contributed to this attack's success.» Тобто «зробимо адреси незгадуваними» дефект не лікує, це лише підвищує ціну перебору. Той самий крок є і в процедурі тестування ескалації — обійти сайт і пошукати сторінки, на яких перевірки авторизації немає; а знайдена сторінка може лишитися відкритою навіть за наявної перевірки, якщо та зроблена за частковим збігом URL.
Моделі доступу й матриця доступів
Модель контролю доступу відповідає на питання, за чим саме система ухвалює рішення: за ролями, за атрибутами чи за звʼязками.
- (Role-Based Access Control) — рішення за ролями: права вішаються не на сутність, а на роль, і сутність успадковує права своїх ролей. Хвороба моделі названа поіменно — («role explosion»).
- (Attribute-Based Access Control) — рішення за атрибутами субʼєкта, атрибутами обʼєкта, умовами середовища й політиками, сформульованими в цих атрибутах і умовах (означення NIST SP 800-162, наведене OWASP).
- (Relationship-Based Access Control) — рішення за звʼязками між ресурсами: «allowing only the user who created a post to edit it».
Звичний порядок «спочатку ролі» варто супроводити рекомендацією джерела: RBAC має довгу історію й лишається популярним, проте для розробки застосунків зазвичай варто віддавати перевагу ABAC і ReBAC. Ламкість складних моделей теж пояснено: політики з ієрархіями, групами й ролями та нечітке розділення адміністративних і звичайних функцій самі породжують дефекти авторизації. Для тестувальника модель визначає стовпець «субʼєкт»: роль для RBAC, атрибути й умови середовища для ABAC, звʼязок «автор ↔ обʼєкт» для ReBAC — а остання вимагає двох акаунтів і чужого обʼєкта, а не двох ролей.
Правило побудови матриці доступів записане дослівно: «For every combination of user type and resource, determine what operations, if any, the user (based on role and/or other attributes) must be able to perform on that resource.» Три наслідки з цього речення:
- матриця будується перебором пар «тип користувача × ресурс», а не переліком знайомих сценаріїв;
- слова «роль та/або інші атрибути» одразу відкривають її під ABAC, а не лише під ролі;
- перебір утілює найменші привілеї — рівно той мінімум прав, який потрібен для роботи.
Клітинка, яку не заповнили, читається як «заборонено»: застосунок має відмовляти за замовчуванням. Вибіркове не рахується — «Validating permissions correctly on just the majority of requests is insufficient», тож дірка в одному рядку знецінює решту. І рядок ставиться на екземпляр, а не лише на тип: право на обʼєкти певного типу не є правом на кожен обʼєкт цього типу.
Мати тести матриця зобовʼязана — вимога джерел, а не ініціатива QA: шпаргалка авторизації каже «Create tests that validate that the permissions mapped out in the design phase are being correctly enforced», список ризиків API вимагає писати тести на механізм авторизації, а веб-редакція Top 10 кладе роботу прямо на нас — «розробники й QA-персонал мають включати функціональний контроль доступу у свої модульні та інтеграційні тести». Перевіряють не лише дозволи, а й поведінку при відмові: , безпечне завершення роботи при провалі перевірки навіть у нештатних умовах, дотримання ABAC-політик. І рядок, який забувають: збої контролю доступу треба логувати й піднімати алерт адміністраторам.
Очікуваний код негативної клітинки канон обмежує з одного боку й лишає відкритим з іншого. Межа є: шпаргалка вимагає, щоб система віддала 403 Forbidden або 404 Not Found — і ніколи 200 OK. А от вибору між цими двома канон не робить: MDN легітимізує політику, за якої замість 403 сервер віддає 404, щоб не підтверджувати саме існування ресурсу. Тож конкретний код клітинка фіксує за домовленістю конкретного сервісу, інакше тест перевіряє уявлення свого автора, а не контракт.
Чому матрицю доводиться переганяти щорелізно, джерело формулює як діагноз: «features are added/modified in updated releases without determining their effect on the application's authorizations» — тому оцінювання авторизацій радять автоматизувати й виконувати на кожен новий реліз, а регресійна шпаргалка називає це проблемою «другого дня». Формально безпека — нефункціональна характеристика за ISTQB, але перевіряється функціональними сценаріями. Побудова клітинок і data-driven прогін — глава «Авторизація в API»; як код і гейти в — глава «Автоматизація security-перевірок для AQA/SDET».
Перевірка доступу живе на сервері
Позиція джерела сформульована як заборона, а не порада: «Developers must never rely on client-side access control checks». І одразу — де перевірка мусить стояти: «Access control checks must be performed server-side, at the gateway, or using serverless function». Тобто захована кнопка, вимкнене поле чи прихований пункт меню не є перевіркою прав.
Три правила, які з цього випливають: перевіряти кожен запит, бо пропущена одна-єдина перевірка вже ставить під загрозу конфіденційність або цілісність; право на тип не є правом на екземпляр, тож перевірку роблять на кожен запит до конкретного обʼєкта чи функції; заборона за замовчуванням — якщо жодне правило не збіглося, доступу немає. В API це формулюється ще жорсткіше: кожен ендпоінт, що приймає id обʼєкта й виконує над ним дію, мусить мати перевірку рівня обʼєкта, яка підтверджує права залогіненого користувача на запитану дію над запитаним обʼєктом.
Ламається це трьома способами, і всі три вже траплялися вище: сторінки без перевірки взагалі; перевірка половинчаста (частковий збіг URL обходиться кодуванням); довіра до входу, керованого клієнтом, — приховане поле профілю або X-Forwarded-For, узятий за адресу клієнта.
Хороша новина: оракул уже у відповіді. Щоб зрозуміти, чи вдався запит до чужого обʼєкта, ані бази, ані логів зазвичай не потрібно — досить того, що повернув сервер. Сценарій із цього й складається: повторити функцію під іншим користувачем.
// Схована кнопка — це ще не перевірка прав.
await expect(page.getByRole('button', { name: 'Видалити проєкт' })).toBeHidden();
// Вердикт дає сервер: той самий виклик повз інтерфейс, під іншим користувачем.
const expectedStatus = 403; // код відмови з контракту сервісу, а не універсальна константа
const res = await userB.delete('/api/projects/1');
expect(res.status(), 'чужий проєкт не має видалятися').toBe(expectedStatus);
Звідси два наслідки. Негативний прогін ставлять на всі операції, не лише на читання: створення, оновлення, видалення, експорт і адміністративні дії теж. І «змінили тільки фронтенд» від прогону не звільняє — регресія доступу ловиться саме серверними сценаріями.
Типові помилки
- «Кнопки в інтерфейсі немає — значить, доступу немає» → виглядає як перевірка прав, а насправді перевірено малюнок: сценарій джерела — саме прямий запит повз фронтенд.
- «Перейшли на UUID — IDOR закрито» → виглядає як виправлення, а насправді складний ідентифікатор лише підвищує ціну підбору: перевірки лишаються обовʼязковими, а витеклу адресу застосунок мусить відхилити.
- «BOLA — це горизонталь, BFLA — вертикаль» → виглядає як акуратна пара, а насправді це два різні розрізи: рівень порушення проти рівня прав жертви. Поле-прапорець робить із горизонталі вертикаль — і саме це пара ховає.
- «IDOR, BOLA і CWE-639 — три назви одного» → виглядає як синонімія, а насправді IDOR ширший за CWE-639, бо покриває ще й path traversal, а BOLA перетинається з ним лише частково.
- «Прогнали матрицю під адміном — усе зелено» → виглядає як покриття, а насправді негативних клітинок під адміном майже не буває: без звичайного користувача й чужого власника перевірки прав не було.
- «Ресурс не залінкований — його не знайдуть» → виглядає як приховування, а насправді forced browsing означений саме як доступ до незалінкованого й лікується авторизацією, а не імʼям файлу.
- «Змінили тільки фронтенд — доступи чіпати не треба» → виглядає як економія прогону, а насправді проблеми з авторизаціями й виникають тому, що фічі змінюють, не визначивши їхнього впливу на права.
Підсумок
- Контроль доступу — це виконання політики «не діяти поза межами дозволів». Його збій стоїть першим у списку ризиків, і цифра поруч — про частку застосунків.
- Дві осі ескалації означені за рівнем прав акаунта-жертви: вертикальна — привілейованіший акаунт, горизонтальна — акаунт із такими самими правами. BOLA і BFLA про інше — про рівень, на якому зламалася перевірка; зводити ці розрізи в один не можна.
- IDOR шукається процедурою, а не інтуїцією: два акаунти, підміна посилань під чужим користувачем, відмова незалежно від вгадуваності ідентифікатора — і так для всіх операцій, не лише для читання.
- Незалінкований і непередбачуваний не означає захищений. Forced browsing живе на розриві між «немає лінка» і «ресурс досяжний», а причина знахідки — авторизація.
- Вердикт про доступ виносить сервер. Клієнтські перевірки не рахуються, перевіряти треба кожен запит і кожен обʼєкт, а незаповнена клітинка матриці читається як «заборонено».
Можливі питання
- «Чим вертикальна ескалація відрізняється від горизонтальної?» — на впізнавання означень. Сильна відповідь визначає обидві через рівень прав жертви й дає приклад на кожну; слабка привʼязує осі до BOLA/BFLA.
- «Що таке IDOR і як ти його шукатимеш?» — чекають механізм і процедуру: два акаунти, підміна посилання, відмова незалежно від вгадуваності id, покриття всіх операцій, а не самого читання.
- «Ми перейшли на UUID — IDOR більше немає?» — пастка на security by obscurity. Правильна відповідь: складний ідентифікатор це додатковий шар, а не заміна перевірки прав.
- «Кнопка адмінки прихована для звичайного користувача. Цього достатньо?» — питання рівно про місце перевірки. Очікують «ні» і сценарій прямого запиту повз інтерфейс під іншим користувачем.
- «Як побудуєш покриття доступів для трьох ролей?» — дивляться, чи будуєте перебором комбінацій «тип користувача × ресурс × операція», чи перелічуєте знайомі сценарії. Плюс бали за негативні клітинки, дефолт «заборонено» і код відмови з контракту сервісу.
- «Чим RBAC відрізняється від ABAC?» — на розуміння моделі, а не визначення напамʼять. Сильна відповідь додає рекомендацію канонічної шпаргалки віддавати перевагу ABAC і ReBAC.
Джерела
- OWASP Top 10:2025 — A01 Broken Access Control — означення контролю доступу й наслідків збою, перше місце категорії та «100% протестованих застосунків», провідні CWE, типові дефекти й два сценарії атаки — forced browsing і контроль доступу, покладений у фронтенд.
- OWASP Top 10:2025 — Introduction — категорія лишається ризиком №1 у редакції 2025, а SSRF влито в неї й окремої позиції не має.
- OWASP Cheat Sheet — Authorization — означення авторизації за NIST, обидві межі «автентифікований проти авторизованого» і теза, що правильної перевірки на більшості запитів недостатньо.
- CWE-918 — Server-Side Request Forgery (SSRF) — запис каталогу фіксує перехід SSRF у категорію A01:2025 і пояснює, чому запит руками сервера обходить контроль доступу.
- OWASP www-community — Forced browsing — канонічне означення атаки, названої серед сценаріїв категорії, і приписування її успіху поганій реалізації авторизації.
- OWASP WSTG — 4.5.3 Testing for Privilege Escalation — означень ескалацій список ризиків не дає: канонічне розведення осей живе в цій процедурі.
Дві осі ескалації: вертикальна і горизонтальна
- OWASP WSTG — 4.5.3 Testing for Privilege Escalation — означення ескалації та її ступеня, канонічне розведення осей, мета тесту, вектори процедури й зауваження про частковий збіг URL.
- CWE-639 — Authorization Bypass Through User-Controlled Key — вісь визначає рівень прав жертви; ключ-прапорець адміністратора дає вертикаль; три місця, де такий ключ живе; назва Horizontal Authorization.
- OWASP Cheat Sheet — Authorization — та сама вісь як horizontal privilege elevation і те, що експлуатація керованого користувачем ключа дає найчастіше горизонтальну ескалацію, рідше вертикальну.
- OWASP API Security Top 10 — 2023 —
API5:2023про складні політики як причину вертикального вектора іAPI3:2023як обʼєднання надмірного розкриття даних із масовим присвоєнням. - OWASP API1:2023 — Broken Object Level Authorization — межа рівнів: порушення на цілому обʼєкті проти порушення на його полях.
- OWASP Top 10:2025 — Software or Data Integrity Failures —
mass assignmentу веб-редакції 2025 стоїть у A08 поіменно як CWE-915, і рамка категорії пояснює, чому це про цілісність.
IDOR: чужий обʼєкт за ідентифікатором
- OWASP Cheat Sheet — Insecure Direct Object Reference Prevention — означення IDOR і три складники, приклад
/users/123, теза «складний ідентифікатор захистом не є» та процедура з двома акаунтами й переліком операцій. - OWASP API1:2023 — Broken Object Level Authorization — законний за задумом ендпоінт і порушення на рівні обʼєкта, типи ідентифікаторів і місця, де вони живуть, наслідки аж до захоплення акаунта, межа з BFLA і недостатність порівняння id сесії з параметром.
- OWASP API Security Top 10 — 2023 — BOLA як ризик номер один редакції 2023 і
API5:2023про доступ до чужих ресурсів та адміністративних функцій. - CWE-639 — Authorization Bypass Through User-Controlled Key — означення класу «підміна ключа» і межі термінів: IDOR ширший за CWE-639, BOLA перетинається з ним лише частково.
- OWASP Cheat Sheet — Authorization — ланка, що звʼязує терміни: керований користувачем ключ пошуку є формою IDOR.
- OWASP Top 10:2025 — A01 Broken Access Control — та сама механіка у веб-редакції списку: перегляд чи редагування чужого акаунта за його унікальним ідентифікатором.
Forced browsing: чого немає в меню, але воно є
- OWASP www-community — Forced browsing — означення атаки й синоніми, механіка перебору й типові цілі, два режими виконання, приклад адреси з логіном і датою, оракул
200та приписування успіху поганій реалізації авторизації. - OWASP WSTG — 4.5.3 Testing for Privilege Escalation — той самий крок усередині процедури тестування ескалації й обхід перевірки за частковим збігом URL кодуванням адреси.
Моделі доступу й матриця доступів
- OWASP Cheat Sheet — Authorization — означення RBAC, ABAC і ReBAC, «role explosion», рекомендація віддавати перевагу ABAC і ReBAC, правило побудови матриці, найменші привілеї, заборона за замовчуванням, недостатність покриття «більшості» запитів, право на тип проти права на екземпляр і вимога мати тести на дозволи.
- OWASP API Security Top 10 — 2023 —
API5:2023про складні політики з ієрархіями й нечітке розділення адміністративних функцій як джерело дефектів авторизації. - OWASP API1:2023 — Broken Object Level Authorization — перевірка прав у кожній функції, що бере ідентифікатор від клієнта, і пряма вимога писати тести на механізм авторизації.
- OWASP Top 10:2025 — A01 Broken Access Control — функціональний контроль доступу в модульних та як робота розробників і QA, а також вимога логувати збої контролю доступу.
- MDN — 403 Forbidden — політика, за якої замість
403віддають404, легітимна, тож очікуваний код клітинки береться з домовленості сервісу. - ISTQB® CTFL Syllabus v4.0.1, §2.2.2 — безпека належить до нефункціональних характеристик, хоча перевіряється функціональними сценаріями.
- OWASP Cheat Sheet — Authorization Testing Automation — більшість проблем з авторизаціями виникає через зміни фіч у нових релізах без оцінки їхнього впливу на права, звідси й порада перевіряти на кожен реліз.
- OWASP Cheat Sheet — Authorization Regression Testing — той самий стан як проблема «другого дня»: разова перевірка на старті наступних релізів не покриває; звідти ж межа очікуваного коду негативної клітинки —
403або404, ніколи200.
Перевірка доступу живе на сервері
- OWASP Cheat Sheet — Authorization — заборона покладатися на клієнтські перевірки й перелік місць, де перевірка мусить виконуватися; перевірка на кожному запиті, право на тип проти права на екземпляр і заборона за замовчуванням.
- OWASP API1:2023 — Broken Object Level Authorization — вимога перевірки рівня обʼєкта на кожному ендпоінті з id і теза, що для вердикту достатньо самої відповіді сервера.
- OWASP WSTG — 4.5.3 Testing for Privilege Escalation — сценарій «повторити функцію під іншим користувачем» і три способи, якими серверна перевірка ламається на практиці.
- OWASP Cheat Sheet — Insecure Direct Object Reference Prevention — перелік операцій, на які планують негативний прогін, і теза, що складність ідентифікатора перевірок не скасовує.
Що таке контроль доступу і чим авторизація відрізняється від автентифікації?
Контроль доступу (access control) — це те, як застосунок домагається, щоб дії користувача не виходили за межі виданих йому дозволів; коли він ламається, наслідком стає читання, зміна чи знищення чужих даних або виконання функції, прав на яку немає. Автентифікація (authentication) відповідає на питання «хто ти», авторизація (authorization) — «чи схвалено саме цю дію для саме цієї сутності»; таке означення авторизації OWASP бере в NIST. Межу варто знати в обидва боки: залогінений користувач не отримує автоматично всього, що система вміє технічно, а неавтентифікований відвідувач цілком буває авторизованим — на публічний вміст. Для тестування це два різні набори негативних сценаріїв: запит зовсім без токена перевіряє автентифікацію, запит із валідним токеном невідповідної ролі — авторизацію, і очікуваний результат для кожного з них фіксують окремо.
Чому зламаний контроль доступу очолює OWASP Top 10 і що означає «100%» поруч із цією категорією?
(Broken Access Control) очолює й редакцію 2025, а цифру поруч читають так: та чи інша форма збою знайшлася в усіх протестованих застосунках — це частка застосунків, а не частка інцидентів чи запитів. За зібраними даними ця категорія трапляється найчастіше, а за кількістю привʼязаних до неї стоїть другою. Ціна одного пропуску при цьому повна: правильна перевірка на більшості запитів проблему не закриває, бо конфіденційність або цілісність ресурсу губить рівно одна пропущена перевірка. На співбесіді цінна не сама цифра, а висновок із неї: доступи покривають перебором, а не вибірково.
Що таке IDOR і з чого він складається?
(Insecure Direct Object Reference) — це коли користувач дістає або змінює чужий обʼєкт, підмінивши ідентифікатор, який застосунок узяв від нього ж і не перевірив на приналежність. Складників рівно три: сам обʼєкт — тікет, документ, чи акаунт; посилання, яким його адресують (числовий id, UUID, slug, номер рахунку, токен); і відсутня перевірка прав саме на цей обʼєкт. Демонстрація в один рядок: відкрити /users/123, замінити хвіст на /users/124 і подивитися, чи показалися чужі дані. Посилання не зобовʼязане жити в шляху — його так само знаходять у query, у заголовку або в тілі запиту. Найнеприємніше тут те, що зовні все штатно: код 200, валідна схема, зелений тест — знахідкою це робить лише питання «а чиї це дані?».
Ми перейшли з числових id на UUID. IDOR закрито?
Ні: складний ідентифікатор змінює вартість , а не наявність дефекту. Підбір GUID справді стає практично неможливим, але це нічого не каже про перевірку прав — а вимога мати її на кожному зверненні до обʼєкта лишається незмінною. Крім підбору, адреса ще й витікає: з логів, з розшарених посилань, з реферера, з експорту чи в тікеті — і застосунок зобовʼязаний відхилити запит навіть на цілком правильний, але чужий ідентифікатор. Тому непередбачувані id працюють як додатковий шар (defense-in-depth), але перевірку прав вони не підміняють. У тесті висновок практичний: перевіряйте і на вгадуваних, і на невгадуваних ідентифікаторах, інакше сценарій мовчки покриває лише половину випадків.
Кнопку адмінської дії приховано для звичайного користувача. Цього достатньо?
Ні, і це питання рівно про те, де стоїть перевірка. Інтерфейс — лише малюнок поверх запитів: прихована кнопка, задизейблене поле чи вирізаний пункт меню не заважають надіслати той самий виклик повз браузер. Канон формулює це як заборону розробникам покладатися на клієнтські перевірки, а місця, де вердикт має ухвалюватися, називає прямо: сервер, або serverless-функція. Серед сценаріїв атаки OWASP окремо описує застосунок, який склав увесь свій контроль доступу у фронтенд, — такий беруть прямим запитом. Сильна відповідь на співбесіді додає перевірку: повторити ту саму функцію під іншим користувачем на рівні API й подивитися, що відповів сервер, — а стан кнопки лишити як окремий, необовʼязковий .
Чим вертикальна ескалація відрізняється від горизонтальної?
(privilege escalation) — це коли користувач дістає ресурси чи функції понад належне йому, а застосунок мав був цю дію заблокувати; ступінь оцінюють за розривом між тим, що належало, і тим, що він фактично здобув. Вертикальна (vertical escalation) — коли жертва привілейованіша: типовий приклад — звичайний акаунт, який дістає адміністративні права. Горизонтальна (horizontal escalation) — коли жертва має такі самі права: класика тут — чужі дані в онлайн-банку. Ключове: вісь визначає рівень прав акаунта-жертви, а не спосіб атаки — той самий прийом дає горизонталь проти рівного користувача й вертикаль, якщо підмінене значення виявилося прапорцем адміністратора. Практичний наслідок для підготовки даних: під вертикаль потрібні звичайний і привілейований акаунти, під горизонталь — двоє рівних користувачів і чужий обʼєкт. Дрібниця, яка виручає: горизонтальну вісь джерела називають по-різному (horizontal escalation, horizontal privilege elevation, Horizontal Authorization) — це одне явище, спільне в усіх означеннях те, що рівень прав у сторін однаковий.
Що таке forced browsing і який у нього оракул?
— це атака перебором: доступ до ресурсів, яких застосунок ніде не згадує, хоча вони лишаються досяжними; ті самі речі трапляються під назвами Predictable Resource Location, File Enumeration, Directory Enumeration, Resource Enumeration. Ключове тут — відсутність посилання, а не відсутність ресурсу: між «немає лінка» і «недосяжно» лежить розрив, на якому атака й тримається. Шукають тимчасові теки й файли, старі бекапи, конфіги — цінність у вихідному коді, облікових даних і внутрішній адресації мережі. Робиться це двома режимами: руками, коли адреса будується на номері чи передбачуваному значенні, або інструментом зі словником типових імен. зручний для чекліста: код 200 на адресу, про яку інтерфейс ніде не згадує, — це знахідка, і далі її розглядають руками. І одразу висновок про виправлення: успіх атаки приписують поганій реалізації авторизації, тож перейменування файлу лише піднімає ціну перебору, а лікує перевірка прав на цій адресі.
Як ти шукатимеш IDOR на проєкті? Опиши процедуру.
Починаю з даних, а не з запитів: заводжу двох користувачів із різним обсягом прав і створюю під кожним його власні обʼєкти. Далі логінюся як перший і повторюю звичайні операції, підставляючи в них посилання на обʼєкти другого — у шляху, query, заголовках і тілі. Очікування одне для всіх спроб — відмова, і не має значення, вгадуваний ідентифікатор чи випадковий. Прогін ставлю не лише на читання: експорт, створення, оновлення, видалення й адміністративні дії теж приймають id, тож кожна з них потребує перевірки. Вердикт знімаю з відповіді сервера — для цього зазвичай не потрібні ні база, ні логи, — а після операцій, які змінюють стан, окремо звіряю під власником, що обʼєкт лишився недоторканим: відмова має підтверджуватися не лише кодом відповіді. І фіксую очікуваний код відмови з контракту сервісу, щоб тест не перевіряв уявлення свого автора.
У формі знайшлося приховане поле profile зі значенням користувача. Що з ним робити?
Приховане поле — такий самий вхід для негативного сценарію, як і видиме: керований користувачем ключ трапляється у трьох місцях, які джерело називає поіменно, — у прихованому полі форми, у параметрі URL і в незашифрованій cookie. Процедура прямо питає, що станеться, якщо тестувальник підмінить значення такого поля на адміністративне. Оракул тут не такий, як у звичайній перевірці валідації: питання не «чи прийняв сервер поле», а «чи зʼявилися разом із ним чужі права» — з'явилися адмінські дії, видно чужі дані, спрацювала закрита функція. Якщо поле фактично працює прапорцем адміністративного статусу, це вже , хоча технічно ми лише підмінили одне значення у формі. У словнику API- це рівень властивостей обʼєкта (API3:2023) — на відміну від BOLA, де перевірка ламається на обʼєкті цілком; а веб-редакція 2025 винесла mass assignment в окрему категорію A08 і назвала поіменно як -915.
Доступ до адмінки обмежено переліком офісних IP. Що тут варто перевірити?
Перше питання — звідки застосунок бере адресу клієнта. Якщо він довіряє заголовку X-Forwarded-For, то обмеження обходиться підміною цього заголовка в запиті, і процедура WSTG називає такий вектор поіменно. Перевірка проста: повторити закритий запит із дозволеною адресою в заголовку й подивитися, чи змінився вердикт; змінився — знахідка, і вона не про мережу, а про довіру до клієнтського вводу. Тут же корисно перевірити споріднений дефект: перевірку авторизації за частковим збігом URL обходять кодуванням адреси, тож варто спробувати той самий шлях у закодованому вигляді. Загальне правило, яке варто озвучити: усе, що надсилає клієнт, — заголовки, приховані поля, параметри — вхід, а не факт.
Чим BOLA відрізняється від BFLA і чи правильно казати «BOLA — це горизонталь, BFLA — вертикаль»?
BOLA (Broken Object Level Authorization) — перший ризик API-редакції 2023: звернення до ендпоінта задумом дозволене, а ламається перевірка на рівні обʼєкта — ідентифікатор підмінили, приналежність не звірили. BFLA (Broken Function Level Authorization) — про інший рівень: атакувальник дістався до ендпоінта чи функції, до яких доступу не мав узагалі. Тобто цю пару розділяє те, на якому рівні пропустили перевірку — на обʼєкті чи на функції, — тоді як вертикаль і горизонталь розділяє рівень прав акаунта-жертви. Це два різні розрізи, і рівність між ними ставити не можна: спрощення «BOLA — горизонталь, BFLA — вертикаль» виглядає акуратно й тому небезпечне — воно ховає найдорожчий випадок, коли підмінене поле виявляється прапорцем адміністратора й горизонтальний за формою вектор дає вертикальний результат. У розмові корисно показати саме це: назвати обидва розрізи окремо й навести приклад, який ламає спрощену пару.
IDOR, BOLA і CWE-639 — це три назви одного явища?
Ні: знак рівності між ними не ставить жодне канонічне джерело. CWE-639 (Authorization Bypass Through User-Controlled Key) — точний машинний ідентифікатор класу «підміна ключа»: авторизація не стає на заваді тому, хто читає чужі дані, підставивши ключ, яким ці дані адресуються. IDOR ширший за цей клас, бо захоплює ще й (CWE-22), а перетин BOLA з CWE-639 лише частковий. Звʼязок між термінами теж канонічний: ключ пошуку, керований користувачем, шпаргалка OWASP відносить до CWE-639 і називає цей клас однією з форм IDOR. На практиці різниця важить у двох місцях: у тікеті, коли треба поставити коректний CWE, і в розмові, де впевнене «це синоніми» одразу читається як поверхневе знання.
Чим RBAC відрізняється від ABAC і ReBAC і що з цього випливає для тест-дизайну?
Модель контролю доступу — це відповідь на питання, на підставі чого система ухвалює рішення про доступ. (Role-Based Access Control) вирішує за ролями: права вішаються на роль, а користувач успадковує права своїх ролей; хвороба моделі має власну назву — (role explosion), коли ролей заводять більше, ніж система здатна осмислено підтримувати. (Attribute-Based Access Control) вирішує за атрибутами субʼєкта й обʼєкта, умовами середовища та політиками, які цими атрибутами й умовами й записані. (Relationship-Based Access Control) вирішує за звʼязком: редагувати допис може лише той, хто його створив. Канонічна шпаргалка OWASP додає рекомендацію, яку варто знати: RBAC має довгу історію й досі популярний, але для розробки застосунків зазвичай раджено віддавати перевагу ABAC і ReBAC. Для тестувальника від моделі залежить сам стовпець «субʼєкт»: у RBAC це роль, в ABAC — набір атрибутів разом з умовами середовища, у ReBAC — наявність звʼязку між користувачем і обʼєктом. Остання відрізняється й підготовкою даних: під неї потрібні два акаунти й чужий обʼєкт, а не дві різні ролі.
Як побудуєш покриття доступів, якщо в системі три ролі?
Не переліком знайомих сценаріїв, а перебором: для кожної комбінації типу користувача й ресурсу визначаємо, які операції цьому користувачеві дозволені — з огляду на роль та/або інші його атрибути. Формулювання «роль та/або інші атрибути» одразу робить матрицю придатною для ABAC, а не лише для рольової моделі; сам перебір і є втіленням — у клітинці лишається рівно той мінімум, який потрібен для роботи. Клітинку, яку не заповнили, читаємо як «заборонено» — застосунок мусить відмовляти за замовчуванням. Два уточнення, за які на співбесіді дають плюс: рядок заводять і на конкретний екземпляр, бо дозвіл працювати з обʼєктами певного типу ще не робить дозволеним кожен окремий обʼєкт; і негативні клітинки важать більше за позитивні — прогін під адміністратором, у якого негативних клітинок майже немає, доступів фактично не перевіряє. Далі матриця стає таблицею даних для прогону, а очікуваний код кожної негативної клітинки береться з контракту сервісу.
Який код має повертати сервер у негативній клітинці — 403 чи 404?
Межа тут одностороння: допустимих відповідей рівно дві — 403 Forbidden і 404 Not Found, а 200 OK із даними виключений. А який саме з двох, канон не диктує: віддавати 404 там, де за логікою стоїть 403, MDN визнає легітимною політикою — так не підтверджують сам факт існування закритого ресурсу. Тому правильна відповідь — «залежить від контракту конкретного сервісу», і саме контракт треба зафіксувати в матриці до того, як писати асерти. Інакше тест перевіряє уявлення свого автора: асерт на 403 червонітиме на сервісі, який свідомо ховає ресурси за 404, і команда звикне його ігнорувати. Практична порада: тримайте очікуваний код полем клітинки, а не константою в тілі тесту, — так зміна політики правиться в одному місці.
Чому матрицю доступів треба переганяти на кожен реліз і чи звільняє «змінили тільки фронтенд»?
Діагноз джерело формулює прямо: більшість проблем з авторизацією виникає тому, що фічі в нових релізах додають і правлять, не оцінивши, як це вплине на права. Тобто одноразової перевірки на старті не буває: шпаргалка OWASP називає це проблемою «другого дня», а шпаргалка про автоматизацію авторизацій радить автоматизувати їх оцінювання й ганяти його на кожен новий реліз. «Змінили тільки фронтенд» виправданням не є: регресію доступу ловлять серверні сценарії, а клієнт у цій темі взагалі не місце перевірки, тож його зміна нічого не доводить. І це вимога самих джерел, а не ініціатива команди: шпаргалка авторизації каже створювати тести, які підтверджують, що спроєктовані дозволи справді виконуються, а веб-редакція Top 10 кладе функціональний контроль доступу в модульні та розробників і QA. У розмові варто додати економічний аргумент: матриця як дані дешева в підтримці — нова роль чи ресурс додає рядки, а не тести.
Що CSRF і SSRF роблять усередині категорії контролю доступу в редакції 2025?
Це факт із самої редакції: серед провідних CWE категорії стоять CWE-200 і CWE-201 (розкриття чутливої інформації), а поруч — CWE-352 (CSRF) і CWE-918 (), причому окремої позиції для SSRF у цій редакції вже немає. Чому там опинився SSRF, пояснює запис каталогу: атакувальник підсовує адресу на несподіваний хост, запит іде ніби руками сервера — і так обходиться контроль доступу на кшталт файрвола, який не пускає його туди напряму. Для CSRF окремого обґрунтування список не наводить: його просто названо серед провідних CWE категорії, і на співбесіді чесніше сказати саме так, ніж вигадувати спільний критерій. Практичний висновок для QA не термінологічний: у переліку негативних сценаріїв на доступ мають бути не лише підміни id, а й поля, куди застосунок приймає адресу чи посилання. Механіка самих атак — окремі теми, але їхнє місце в карті ризиків знати корисно, бо саме про це запитують після слів «а що ще входить у A01».
Що, крім самого коду відповіді, варто перевіряти в поведінці системи при відмові?
По-перше, дефолт: не збіглося жодне правило — доступу немає, і це окремий сценарій, а не наслідок інших. По-друге, поведінку в нештатних умовах: перевірка мусить завершуватися безпечно навіть тоді, коли щось пішло не так усередині, — падіння сервісу авторизації не має відкривати доступ. По-третє, відповідність політикам ABAC, якщо рішення ухвалюється за атрибутами: змінилася умова середовища — змінився вердикт. По-четверте, побічний ефект: відмова має бути справжньою, тож після негативного виклику на зміну стану варто перевірити під власником, що обʼєкт не змінився. І рядок, який забувають найчастіше: збої контролю доступу мають лишати запис у лозі й піднімати алерт адміністраторам — застосунок, який мовчки відмовляє тисячу разів поспіль, не дає жодного шансу помітити перебір.
Три кейси з робочого життя: як довести (або спростувати) тікет «прав немає, бо кнопки немає», як перетворити матрицю доступів на прогін, який реально червоніє, і як розібрати дві знахідки — незалінковану адресу та приховане поле, — не переплутавши осі ескалації. Скрізь — що дивитися і чому саме це.
Кейс 1. «Кнопки немає» — це ще не доказ
Тікет закрили з коментарем: «звичайний учасник проєкту кнопки «Видалити проєкт» не бачить, права працюють». Питання, яке варто поставити першим: що саме перевірили — прийняте рішення сервера чи стан пікселів? Інтерфейс лише малює те, що йому дозволили намалювати, а DELETE /api/projects/1 летить і без нього.
| Що перевірили | Що це насправді доводить |
|---|---|
| Кнопки «Видалити проєкт» немає в DOM | лише те, що фронтенд її не відрендерив для цієї ролі |
| Пункт меню задизейблено | те саме, іншим локатором |
Прямий DELETE /api/projects/1 під учасником без права видалення повернув відмову | що перевірка стоїть на сервері — оце і є доказ |
| Проєкт після спроби на місці (звірено під власником) | що відмова справжня, а не косметична |
Перші два рядки не зайві — вони фіксують зручність інтерфейсу. Але вердикт про права дають рядки третій і четвертий, і саме їх у тікеті не було.
import { test, expect } from '@playwright/test';
const BASE = 'https://app.example.com';
// два API-контексти: кожен зі своїм токеном, спільного стану між ними немає
test('учасник без прав не видаляє проєкт прямим викликом повз інтерфейс', async ({ playwright }) => {
const asOwner = await playwright.request.newContext({
baseURL: BASE,
extraHTTPHeaders: { Authorization: `Bearer ${process.env.TOKEN_OWNER}` },
});
const asOther = await playwright.request.newContext({
baseURL: BASE,
extraHTTPHeaders: { Authorization: `Bearer ${process.env.TOKEN_MEMBER}` },
});
// 1) власник створює обʼєкт, ідентифікатор беремо з відповіді, а не з фікстури
const created = await asOwner.post('/api/projects', { data: { name: 'acl-demo' } });
expect(created.status()).toBe(201);
const projectId = (await created.json()).id;
// 2) учасник без права видалення пробує ту саму операцію напряму
// 403 — код із контракту цього сервісу, а не універсальна константа
const attempt = await asOther.delete(`/api/projects/${projectId}`);
expect(attempt.status(), 'сервер мусить відмовити, а не покластися на приховану кнопку').toBe(403);
// 3) побічний ефект: відмова має бути справжньою
const stillThere = await asOwner.get(`/api/projects/${projectId}`);
expect(stillThere.status(), 'проєкт мусить лишитися на місці').toBe(200);
});
Що дивитися і чому:
- Найдорожча знахідка виглядає як успіх.
200 OKз валідним тілом на чужий обʼєкт не підсвітиться жодною схемною перевіркою — червоним його робить лише питання «чиї це дані». - Код беріть із контракту сервісу. Канон обмежує його з одного боку:
403або404, ніколи200. Вибір між цими двома — політика продукту:404навмисно не підтверджує існування закритого ресурсу. У цьому сервісі учасник без прав отримує403, а звернення до чужого проєкту —404(кейс 2), і обидва коди записані в контракті, а не вгадані в тесті. - Крок 3 не косметичний. Код відповіді каже, що сервер відмовив, а стан обʼєкта — що дія справді не виконалася; на операціях зі зміною стану це різні твердження.
- на приховану кнопку лишіть, але окремим тестом. Він про UX; коли впаде саме він, ви точно знатимете, що зламався інтерфейс, а не права.
Кейс 2. Матриця доступів як таблиця рішень і прогін по ній
Три ролі в проєкті — owner, member, viewer. Матрицю будують не переліком знайомих сценаріїв, а перебором пар «тип користувача × ресурс»: у клітинці стоїть операція й очікуваний результат. Порожню клітинку читаємо як «заборонено», а окремим рядком заводимо чужий обʼєкт того самого типу: дозвіл на тип ще не є дозволом на кожен його екземпляр.
| Операція | owner (свій проєкт) | member (свій проєкт) | viewer (свій проєкт) | будь-хто (чужий проєкт) |
|---|---|---|---|---|
GET /api/projects/:id | 200 | 200 | 200 | 404 |
PATCH /api/projects/:id | 200 | 200 | 403 | 404 |
DELETE /api/projects/:id | 204 | 403 | 403 | 404 |
POST /api/projects/:id/members | 201 | 403 | 403 | 404 |
GET /api/projects/:id/export | 200 | 200 | 403 | 404 |
Стовпець «чужий проєкт» тут заповнено 404 — це рішення конкретного сервісу (не підтверджувати існування), і воно має бути записане в контракті, а не в голові автора тесту. Далі таблиця стає даними:
import { test, expect, type APIRequestContext } from '@playwright/test';
type Role = 'owner' | 'member' | 'viewer';
type Cell = {
name: string;
role: Role;
foreign: boolean;
expected: number;
call: (api: APIRequestContext, id: string) => Promise<{ status: () => number }>;
};
const matrix: Cell[] = [
{ name: 'DELETE проєкту', role: 'owner', foreign: false, expected: 204, call: (api, id) => api.delete(`/api/projects/${id}`) },
{ name: 'DELETE проєкту', role: 'member', foreign: false, expected: 403, call: (api, id) => api.delete(`/api/projects/${id}`) },
{ name: 'DELETE проєкту', role: 'viewer', foreign: false, expected: 403, call: (api, id) => api.delete(`/api/projects/${id}`) },
{ name: 'DELETE проєкту', role: 'owner', foreign: true, expected: 404, call: (api, id) => api.delete(`/api/projects/${id}`) },
{ name: 'PATCH проєкту', role: 'viewer', foreign: false, expected: 403, call: (api, id) => api.patch(`/api/projects/${id}`, { data: { name: 'x' } }) },
];
for (const cell of matrix) {
const scope = cell.foreign ? 'чужий' : 'свій';
test(`${cell.role} / ${cell.name} / ${scope} → ${cell.expected}`, async ({ apiFor, ownProject, foreignProject }) => {
const api = await apiFor(cell.role);
const id = cell.foreign ? foreignProject.id : ownProject.id;
const res = await cell.call(api, id);
expect(res.status()).toBe(cell.expected);
});
}
Що дивитися і чому:
- Негативні клітинки — це і є тест доступів. Прогін під адміністратором, у якого негативних клітинок майже немає, перевіряє що завгодно, тільки не права: без звичайного користувача й чужого власника доводити нічого.
- Очікуваний код — поле клітинки, а не константа в тілі тесту. Продукт змінить політику з
403на404— правка буде в одному місці, а не в двадцяти асертах. - Нова роль чи ресурс додає рядки, а не тести. Саме тому матрицю дешево ганяти на кожен реліз, а це і є рекомендація канону: фічі змінюють, не оцінивши їхнього впливу на права, тож разова перевірка на старті не рятує.
- Рядок «чужий обʼєкт» обовʼязковий у кожній операції. Без нього матриця перевіряє лише ролі й спокійно пропускає :
viewerне має видаляти нічого, але йownerне має видаляти чуже. - Читанням не обмежуйтеся. Експорт, створення, оновлення, видалення й адміністративні дії теж приймають ідентифікатор — і теж мусять перевірятися.
Кейс 3. Дві знахідки за спринт: незалінкована адреса і поле-прапорець
Знахідка A — незалінкований експорт. У листі-нагадуванні знайшлося посилання на звіт із логіном і датою в шляху. Схема адреси очевидна, і це вже половина : словник тут не потрібен, досить підставити чужий логін і вчорашню дату.
# ця адреса ніде в інтерфейсі не згадується; сесія — звичайного користувача
curl -s -o /dev/null -w '%{http_code}\n' \
-H "Cookie: session=$SESSION_MEMBER" \
'https://app.example.com/reports/export?user=ivanenko&date=20260801'
# 200 ← знахідка: адресу «яку ніхто не знає» віддали без перевірки прав
тут простий: код 200 на адресу, про яку інтерфейс ніде не згадує, — це знахідка, і далі її розглядають руками (що саме віддали, чи є там персональні дані, чи працює це для довільного логіна). Найважливіше — правильно записати виправлення: перейменування файлу або хеш у назві лише піднімають ціну перебору, бо успіх приписують поганій реалізації авторизації. Тому в тікеті вимога звучить як «перевіряти права на цьому маршруті», а не «зробити адресу незгадуваною». Побіжно варто спробувати той самий шлях у закодованому вигляді: перевірка за частковим збігом URL так і обходиться.
Знахідка B — приховане поле профілю. У формі редагування налаштувань лежить поле, якого користувач не бачить:
<input type="hidden" name="profile" value="user">
Керований користувачем ключ живе у трьох названих джерелом місцях — прихованому полі, параметрі URL і незашифрованій cookie, — і кожне з них є входом негативного сценарію. Підміняємо значення на адміністративне й дивимося не на те, чи прийняв сервер поле, а на те, чи зʼявилися разом із ним чужі права: чи відкрилися закриті пункти меню, чи проходять адміністративні виклики, чи видно чужі дані.
| Знахідка | Що підмінили | Рівень прав жертви | Вісь | Куди дивитися далі |
|---|---|---|---|---|
| A: експорт за логіном і датою | логін у query | такий самий користувач | горизонтальна | усі маршрути звітів, не лише цей |
B: profile у прихованому полі | значення профілю | адміністратор | вертикальна | інші місця, де роль приходить від клієнта |
Що дивитися і чому:
- Вісь визначає рівень прав жертви, а не спосіб підміни. У знахідці B ми зробили технічно те саме, що в A, — підмінили значення у запиті, — але жертвою став привілейований рівень, тож це й інший пріоритет тікета.
- «Поле прийняли» — ще не баг. Сервер міг мовчки проігнорувати значення. Знахідкою це стає лише тоді, коли разом із полем приїхали права.
- Обидві знахідки — одна причина. І незалінкована адреса, і поле- кажуть те саме: рішення про доступ ухвалюють не там, де треба, — на вході від клієнта або взагалі ніде.
- Логи теж частина вимоги. Збої контролю доступу мають потрапляти в лог і піднімати алерт: сервіс, який мовчки відмовив тисячу разів поспіль, не дає жодного шансу помітити перебір адрес.
Що саме ламається
- Знаю означення контролю доступу, розрізняю автентифікацію («хто ти») і авторизацію («чи схвалено цю дію для цієї сутності») та памʼятаю обидві межі: залогінений не означає «можна все», неавтентифікований не означає «не можна нічого».
- Памʼятаю, що очолює :2025, а «100%» поруч — це частка протестованих застосунків зі знайденим збоєм, а не частка інцидентів.
- Знаю, що редакція 2025 віднесла CSRF і до цієї ж категорії, а окремої позиції для SSRF у ній уже немає.
Дві осі ескалації
- Знаю означення і те, що її ступінь міряють різницею між належним і здобутим.
- Можу розвести вертикальну й через рівень прав акаунта-жертви, а не через спосіб , дати приклад на кожну й сказати, які акаунти під це готують.
- Можу назвати вектори процедури — підміна
groupIDіorderID, приховане поле профілю,X-Forwarded-Forзамість справжньої адреси клієнта, обхід сторінок у пошуку тих, де перевірки немає, — і памʼятаю, що перевірку, зроблену на частковий збіг URL, обходять закодованою адресою.
IDOR і сусідні терміни
- Можу назвати три складники : обʼєкт, посилання на нього і відсутню перевірку прав на рівні цього обʼєкта.
- Знаю, що BOLA (
API1:2023) ламається на рівні обʼєкта при законному за задумом , BFLA — це вже доступ до самої функції, і що пара «BOLA — горизонталь, BFLA — вертикаль» хибна: це різні розрізи, а поле- робить із горизонталі вертикаль. - Не ставлю знак рівності між IDOR, BOLA і -639: IDOR ширший (покриває ще й ), BOLA перетинається з CWE-639 лише частково.
- Можу пояснити, чому UUID чи GUID — додатковий шар, а не заміна перевірки, і чому витеклу адресу застосунок мусить відхилити.
- Знаю процедуру пошуку: два акаунти з різним обсягом прав, підміна посилань під чужим користувачем, очікувана хоч на вгадуваний, хоч на випадковий ідентифікатор — і так на кожну операцію, не лише на читання.
Forced browsing
- Знаю означення й те, що ключове в ньому — «застосунок не посилається», а не «ресурсу немає».
- Памʼятаю (
200на адресу, якої в інтерфейсі немає, — знахідка на ручний огляд) і те, що причина знахідки — авторизація: непередбачувана назва лише піднімає ціну перебору.
Моделі доступу й матриця
- Знаю різницю , і , термін «вибух ролей» як хворобу першої моделі та рекомендацію канону віддавати перевагу ABAC і ReBAC.
- Можу побудувати матрицю перебором пар «тип користувача × ресурс», знаю, що незаповнена клітинка означає відмову за замовчуванням, і памʼятаю, що вибіркове не рахується — правильної перевірки на більшості запитів недостатньо.
- Знаю, що негативна клітинка припускає лише
403чи404і ніколи200, а який саме з двох — беруть із контракту сервісу.
Де живе перевірка і як її ганяти
- Знаю, що вердикт виносить сервер, або serverless-функція, а стан інтерфейсу (схована кнопка, задизейблене поле) перевіркою прав не є.
- Можу назвати три способи, якими серверна перевірка ламається: її немає взагалі, вона половинчаста, вона довіряє керованому клієнтом входу.
- Памʼятаю, що право на тип ресурсу не є правом на кожен його екземпляр, тож перевірка потрібна перед кожним зверненням до конкретного обʼєкта чи функції.
- Памʼятаю, що негативний прогін ставлять на всі операції з посиланням на обʼєкт, що «змінили тільки фронтенд» від нього не звільняє, а самі збої контролю доступу мають потрапляти в лог і піднімати алерт.
Квіз
Перед стартом
- Питань: 15
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Що таке контроль доступу (access control)?