Вразливості бізнес-логіки
Зміст
Більшість попередніх глав розділу — про те, як застосунок робить те, чого йому не казали: підмішує команду в дані, XSS проносить скрипт у вивід, CSRF надсилає чужий запит руками браузера. Тут навпаки: код виконує рівно те, що в ньому написано, а помилка сидить у самій інструкції — задуманий процес не збігається з тим, що потрібно бізнесу. Тому в темі немає пейлоада, який можна вставити в поле, і немає сигнатури, за якою це знайшов би сканер.
Для QA це найближчий до звичайного тестування клас — і саме тому найнеприємніший: він вимагає знати правила процесу, а не список . Глава — канонічний виклад теми на сайті.
Що таке вразливість бізнес-логіки
(business logic vulnerability) — це дефект у тому, як застосунок реалізує задуманий процес: код робить те, що йому сказали, — просто сказане не збігається з потребою бізнесу. Ціну класу називає одразу: такі дефекти зазвичай одні з найважчих для виявлення, зазвичай специфічні для конкретного застосунку — і водночас одні з найруйнівніших, якщо їх проексплуатувати.
Чому мовчить автоматика: у цих багів немає сигнатури, за якою зіставляти; інструментам важко зрозуміти контекст; вони перевіряють, що процес працює у валідних ситуаціях, і логічних вразливостей виявляти нездатні. Найточніше — перелік того, що лишається людині: інструмент не має способу визначити, чи здатен користувач обійти потік процесу редагуванням параметрів, вгадуванням імен ресурсів або , і не має механізму навіть запідозрити такий стан справ.
У карті клас живе в A06:2025 Insecure Design () — «відсутній або неефективний дизайн контролю». Дефекти бізнес-логіки названі там прямо, зокрема брак опису небажаних і неочікуваних змін стану. Межу між дизайном і реалізацією OWASP проводить навмисно: різні першопричини, різний момент виникнення, різне лікування — бездоганна реалізація не виправляє небезпечного дизайну, бо потрібного контролю там ніколи не проєктували. Тож знахідку не закривають фіксом у коді: вона повертає команду до дизайну.
Порожній результат спроби теж нічого не доводить: успіх атаки залежить від глибини вразливості, сили самої атаки й дієвості контрзаходів, і не кожна атака вдається (до речі, в ISTQB attack означає тестовий підхід, а дію зловмисника — окремий запис security attack). А ореол екзотики знімає сам WSTG: тестування логічних дефектів подібне до типів тестування, які застосовують функціональні тестувальники — тих, що зосереджені на логіці або на скінченних станах. Тож і вхідна точка не пейлоадна: випишіть правила бізнес-функції, а тоді перевірте, чи застосунок їх примушує виконувати.
Обхід workflow: правильні кроки в неправильному порядку
(circumvention of work flows) — за означенням WSTG це будь-яка , що дозволяє зловживати застосунком у спосіб, який дає обійти задуману послідовність кроків бізнес-процесу (конфігурований процес CI — «воркфлоу» GitHub Actions — це інша тема, тож оригінал тут лишається латиницею).
Вимога подвійна, і другу її половину забувають частіше: користувач має пройти кроки в правильному порядку, а якщо процес обірвано без коректного завершення — всі дії й породжені ними дії відкочуються або скасовуються. Застосунок мусить знати сам себе (self-aware) і не давати обходити, пропускати чи повторювати кроки.
Два приклади з джерела мають спільну форму. Перший: починають, бали лягають на лояльнісний рахунок, транзакцію скасовують — і рівень накручено, нічого не купивши. Другий: модерація фільтрує заборонені слова під час публікації, але на редагуванні допис уже ніхто не порівнює зі списком. В обох перевірка існує — вона просто стоїть не на всіх переходах стану.
Формулювання очікування тут не наше: перевіряють спроби пропустити, повторити й переставити кроки, і результатом кожної має бути , а не просування. Готового чеклиста під конкретний продукт не буде: такі вразливості дуже специфічні для системи, а ретельні ручні misuse cases розробляються з вимог і use case-ів.
test('оплата без пройденого кроку доставки відхиляється', async ({ request }) => {
const order = await createOrder(request); // крок 1 пройдено
const res = await request.post('/api/checkout/pay', {
data: { orderId: order.id, method: 'card' }, // крок 2 пропущено
});
expect(res.ok()).toBeFalsy(); // відмова, а не просування
expect(await paymentsFor(order.id)).toHaveLength(0); // і жодного побічного ефекту
});
На боці API це підступніше, і причину названо прямо: кожен автентифікується й авторизується окремо, тож традиційні перевірки доступу таких проблем часто не виявляють — під це шпаргалка OWASP REST Security тримає окремий перелік «Preventing Out-of-Order API Execution» (чи можна викликати ендпоінти поза послідовністю, чи кожен валідує поточний стан процесу, чи придатні токени до між кроками).
Обрив на півдорозі потрапляє в нову категорію редакції 2025 — A10 Mishandling of Exceptional Conditions (), що зібрала 24 про неправильну обробку помилок, логічні помилки й «падіння у відкритий стан». Її рецепт збігається з вимогою WSTG: обірвану на середині транзакцію відкочують по кожній частині й починають наново — це і є failing closed. Розкриття даних через помилку — інша частина тієї самої категорії, її дім — глава «Security misconfiguration і розкриття даних».
Підтвердження транзакції: другий фактор на операцію, а не на вхід
(transaction authorization) — це подання другого фактора саме на чутливу операцію (скажімо, на переказ), щоб система перевірила право користувача її виконати. Це не вхід у систему, і змішування цих двох речей дає найдешевшу атаку: якщо вхід і підтвердження виглядають однаково, шкідливе ПЗ використає перші облікові дані для автентифікації, а другі — для авторизації шахрайської операції.
Принцип, на якому все тримається, — WYSIWYS (What You See Is What You Sign): метод підтвердження мусить дозволяти користувачеві розпізнати й підтвердити саме ті дані, які значущі для цієї операції. Порожнє «введіть код із SMS» без опису операції — дефект, а не косметика: брак змістовної підказки легко зловживається соціальною інженерією й шкідливим ПЗ. Решта вимог прямо перетворюється на тести:
- Одне підтвердження на сесію дорівнює підтвердженню чого завгодно: якщо авторизаційні дані питають раз, ними можна авторизувати будь-яку операцію протягом сесії. Звідси — кожен набір авторизаційних даних унікальний для кожної операції.
- Контроль — на сервері: результат авторизації не має змінюватися від правки даних дорогою від клієнта до сервера.
- Кроки не переставляються й не пропускаються, а зміна даних має анулювати підтвердження — знецінити раніше введені авторизаційні дані (наприклад, згенерований OTP) і сам ; перед виконанням стоїть фінальний контроль, що перевіряє факт авторизації.
- Перебір коду ламає процес, а не лічильник: після певної кількості невдалих спроб весь процес підтвердження починається наново. Конкретного числа джерело не дає — це рішення продукту.
І окремо сценарій, який шпаргалка називає дослівно: у запит, що авторизує операцію, додають нові параметри даних — і погано реалізований процес авторизує початкову операцію, а виконає шахрайську.
TOCTOU: вікно між перевіркою і використанням
TOCTOU (Time-of-check Time-of-use) — продукт перевіряє стан ресурсу перед використанням, але стан устигає змінитися так, що результат перевірки стає недійсним (CWE-367). Точність тут не педантизм: TOCTOU не синонім гонки — у каталозі MITRE він стоїть нащадком CWE-362 «Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')», тобто гонка ширший клас, а TOCTOU — її впізнаваний вид із формою «перевірив → використав» (поширене написання TOCCTOU MITRE називає найімовірнішою одруківкою від TOCTTOU — у трекері шукайте всі варіанти).
Гонка як браузерний механізм і часта причина — тема розділу про веб: «Виконання JavaScript та event loop». Тут та сама механіка як безпекова слабкість: такою вона стає, коли може вплинути на стан ресурсу між перевіркою і використанням — на спільний файл, памʼять, навіть змінну в багатопотоковій програмі. Демонстраційний приклад MITRE — підміна файлу симлінком після виклику access(), після якої привілейована програма працює з чужим файлом; у переліку спостережених прикладів там же стоїть -2015-1743, де файл підмінили після перевірки, але до виконання. Наслідком буває і доступ до чужого на читання й запис, і те, що такий запис може не потрапити в логи.
У вебзастосунку ознака вразливого місця формулюється одним рядком: будь-яка операція, що читає значення, ухвалює за ним рішення й записує значення, є потенційною гонкою. Класика — ендпоінт перевіряє баланс і списує кошти без блокування: два одночасні запити обидва проходять перевірку й обидва списують.
Наслідки названі поіменно: бали лояльності висмоктуються, баланси йдуть у мінус, одноразові купони гасяться багато разів, а бонуси «один на акаунт» стають «багато на акаунт». Аргумент «вікно надто вузьке» джерело знімає прямо: інструменти тривіально надсилають десятки запитів у межах однієї мілісекунди — якщо вікно існує взагалі, вважайте його експлуатованим.
Заходів CWE називає чотири: не робити перевірку перед використанням узагалі (першопричини це не усуває, зате прибирає хибне відчуття безпеки), блокувати до перевірки, а не після, перевіряти ресурс ще раз після використання і — якщо атомарність недосяжна — скорочувати проміжок між перевіркою й дією, розуміючи, що проблему це не виправляє. У вебі все зводиться до атомарності й до застереження, яке знімає найпоширенішу відмовку: на дефолтному більшості баз перевірка й оновлення виконуються окремо, і між ними прослизає інша транзакція.
Позиція за замовчуванням для тестування — вважати одночасність реальною : якщо два запити можуть змагатися, вони змагатимуться, тож напишіть тест, який їх зіштовхує. І межа вголос: () знаходить лише деякі випадки цієї слабкості, тож його мовчання доказом не є.
test('одноразовий ваучер гаситься рівно один раз', async ({ request }) => {
const redeem = () => request.post('/api/cart/voucher', {
data: { cartId, code: 'WELCOME-1' },
});
const responses = await Promise.all([redeem(), redeem(), redeem()]);
const accepted = responses.filter((r) => r.ok());
expect(accepted).toHaveLength(1);
});
Single-packet attack: коли вирішують мілісекунди
Promise.all шле запити з одного процесу, але доїжджають вони по-різному: мережевий джитер (network jitter) розтягує прибуття, і саме через нього вразливості з вузьким вікном історично було надзвичайно важко знаходити.
— прийом, який це прибирає: він змушує 20-30 запитів прибути на сервер одночасно, незалежно від мережевого джитера. Увів його James Kettle (PortSwigger Research) на Black Hat USA 2023. Механіка тримається на HTTP/2, бо той дозволяє надсилати запити конкурентно одним зʼєднанням, тоді як у HTTP/1.1 вони мусять іти послідовно: від кожного запиту притримують крихітний фрагмент (заголовки без прапорця END_STREAM, а порожній DATA-фрейм із ним — наостанок) і «дозавершують» усі 20-30 одним TCP-пакетом, для чого TCP_NODELAY має бути вимкнений — фінальні фрейми збирає докупи саме алгоритм Нейгла. Із HTTP/1 прийом несумісний, тож підтримка HTTP/2 на цілі перевіряється першою.
Виграш автор виміряв на пачці з 20 запитів, надісланій за 17 000 км із Мельбурна в Дублін: медіанний розкид last-byte sync — 4 мс проти 1 мс у single-packet attack, тобто в 4-10 разів дієвіше; на реальній вразливості — близько 30 секунд проти понад двох годин. Живе прийом у розширенні Turbo Intruder, а з Burp Suite 2023.9 — і в самому Repeater із групами паралельних запитів.
І дві пастки інтерпретації. Пачка обробилася послідовно — це ще не «застосунок серіалізує запити»: PHP за замовчуванням блокує за ідентифікатором сесії, тож кожен запит має йти зі своєю сесією. Нерівний тайминг — це ще не поведінка застосунку: стабільно різний навіть за single-packet означає на боці бекенду, а не блокування в логіці.
Маніпуляція ціною, кількістю й купонами
Найупізнаваніший підклас — (price manipulation): сервер перераховує підсумок із ціни, надісланої клієнтом, замість того щоб узяти власну з бази, і користувач платить один цент за телевізор. У WSTG той самий сюжет — користувач бачить сторінку підсумку, а тоді повертається на неї в тій самій валідній сесії, підставляє нижчу ціну й завершує покупку.
Правило, яке знімає весь клас, коротке: ніколи не приймати від клієнта ціну, проміжний підсумок, податок чи загальну суму. Від клієнта — тільки ідентифікатори товарів і кількість, решту сервер рахує сам; зі знижками так само — приймається код купона, а валідація й застосування на сервері. Готовий набір кейсів теж із джерела:
- відʼємна кількість у кошику, щоб отримати «повернення» проти вартості іншого товару;
- купон, застосований повторно або до товарів, для яких він не призначався;
- стекінг промо: кілька акцій, кожна з яких мала працювати окремо, разом опускають ціну нижче собівартості;
- одноразовий ваучер, погашений двома паралельними запитами — перетин із TOCTOU.
Окремого іменованого тесту під маніпуляцію ціною у WSTG v4.2 немає: приклад із цінами живе у вступі до розділу бізнес-логіки, а перевірку даних тримає WSTG-BUSL-01 «Test Business Logic Data Validation». Його вимога ширша за форму: логічно валідні дані мають прийматися і на фронтенді, і безпосередньо на сервері, бо перевірка лише локально лишає застосунок уразливим через та на стиках з іншими системами; дані не стають довіреними від того, що вже потрапили в систему. Від аналізу граничних значень джерело цей тест відмежовує: тут складніше, і здебільшого перевірити в точці вводу не вийде — доводиться звірятися з іншою системою.
Як шукають руками: перехоплювальним проксі дивляться POST/GET, вишукуючи змінні на кшталт вартості й кількості, а тоді обстрілюють поле логічно «невалідними» даними — ідентифікаторами, яких не існує, або значеннями, що не вкладаються в бізнес-логіку. Ціль сформульована як твердження про бекенд: усі перевірки відбуваються на сервері й їх не можна обійти.
Патерни зловживання: перелік, проти якого проєктують
Шпаргалка OWASP тримає окремий розділ «Abuse Patterns to Design Against» — тобто (abuse pattern) є предметом проєктування, а не лише реагування після інциденту. До вже перелічених купонних патернів WSTG додає накрутку балів циклом «почати транзакцію → скасувати» і контроль, що стоїть лише на створенні. Разом це стартовий набір кейсів для будь-якого процесу з грошима, балами чи лімітами: кожен ліміт «один на акаунт» перевіряється двічі — послідовно й одночасно, кожен контроль зі створення перевіряється на редагуванні, кожне скасування — на відкат усього, що процес устиг нарахувати.
Межу автоматизації джерело проводить точно: автоматизація бізнес-логіки неможлива — це лишається ручним мистецтвом, що спирається на знання повного бізнес-процесу та його правил. Але сказано це про сценарій, а не про перевірку: щойно сценарій відібрано, перевірка автоматизується, і чинна шпаргалка називає три її форми — тест на інваріант (на кожен інваріант із пишеться тест, який упаде, якщо інваріант порушено), тест на одночасність і тест на порядок кроків з відмовою як очікуваним результатом.
Анти-автоматизація: один контроль не тримає
Зловживання логікою рідко лишається поодиноким — його масштабують автоматизацією, і патерни такого трафіку каталогізовані окремим проєктом OWASP, Automated Threats to Web Applications (OAT-001…OAT-021). Ціль контролю — не «заблокувати всіх ботів» (пошукові й моніторинг легітимні), а підняти вартість зловживання автоматизацією. Звідси шаруватість: один контроль крихкий, а запит, який виглядає людським на одному шарі (нормальний IP, розвʼязана ), може провалитися на іншому — десять оформлень замовлення за тридцять секунд різними картками.
CAPTCHA тут не запобіжник: канон називає її контролем (defense in depth), який робить перебір довшим і дорожчим, і окремо каже, що вона не заміняє блокування. Ще одна межа: головоломки на вводі корисні проти зловживання функціональністю, але від DoS-атак не захищають. Повний розбір контролів — глава «Автентифікація: атаки й тестування».
Для AQA тут є прикладний бік: (приховане поле форми, яке заповнюють лише боти, після чого запит відхиляють) з боку автотесту виглядає як баг застосунку, а (виявленому боту не віддають 403, а прогресивно сповільнюють відповіді) виглядає як . Обидва є свідомою поведінкою захисту, і поводження автотестів із ними узгоджують наперед.
Abuse cases: сценарій зловживання, що став вимогою
Звідки беруться сценарії? З вимог. Abuse case — це use case, у якому зі злим наміром шкодять системі або іншим акторам (означення ISTQB); друге означення дивиться з боку функції — це спосіб використання функції, не передбачений розробником.
Одразу про статус: шпаргалка OWASP під цю техніку архівна, і причина названа в ній самій — рецензенти зауважили, що abuse cases рідко застосовують на практиці, каркас видається важким, а опублікованих прикладів мало. Тож тут abuse case — описана техніка, а не чинна рекомендація. Жива вона під іншою назвою: WSTG вживає misuse case у тому самому сенсі й ставить його методом, а не опцією — «розробіть misuse case і спробуйте обійти кожен виявлений логічний потік». Що шпаргалка вимагає від техніки, якщо її застосовують:
- відібрані abuse cases мусять стати вимогами безпеки — секцією специфікації або user story; інакше їх не оцінять за вартістю й не реалізують, а унікальний ідентифікатор кожного потрібен, щоб трасувати його впродовж проєкту;
- моделі abuse case — паливо для конкретних тестів безпеки, а автотести під них ще й відстежують дієвість контрзаходів і те, що контрзахід лишився на місці після виправлень;
- QA названий у переліку учасників поіменно — аналітик якості або функціональний тестувальник як людина, що розуміє, як функція має працювати, як не має і що змушує її ламатися.
І окремо клас, який шпаргалка називає abuse of functionality, він же «business logic attack»: він залежить від дизайну й реалізації функцій, а приклад класичний — використання потоку для перебору акаунтів.
Типові помилки
- Виглядає як «скан зелений — чисто», а насправді у цих багів немає сигнатури, а контексту сканер не розуміє.
- Виглядає як «користувач так не зробить», а насправді непередбачений спосіб використання функції і є означенням класу.
- Виглядає як «у нас же транзакція в базі», а насправді на дефолтному рівні ізоляції перевірка й оновлення ідуть окремо, і між ними прослизає інша транзакція.
- Виглядає як «вікно в мілісекунди — теорія», а насправді виміряний розкид single-packet attack — 1 мс.
- Виглядає як «ціна ж перевіряється у формі», а насправді локальна перевірка лишає застосунок уразливим через проксі й на стиках із суміжними системами.
- Виглядає як «модерація стоїть, слово не пройде», а насправді контроль спрацював на створенні, а на редагуванні допис уже ніхто не звіряє.
- Виглядає як «код підтвердження прийшов — операція та сама», а насправді дані могли змінитися після авторизації; джерело називає це TOCTOU.
- Виглядає як «тест то падає, то ні — флак», а насправді це може бути тарпітинг або honeypot-поле.
Підсумок
- Логічна вразливість — це дефект задуманого процесу, а не вводу. Сигнатури немає, тож знаходить її людина, яка знає правила процесу, а не інструмент.
- Багатокроковий процес перевіряється трьома спробами: пропустити, повторити, переставити кроки; очікуваний результат кожної — відмова, а обірваний процес відкочує все, що встиг нарахувати.
- TOCTOU — не синонім гонки, а її вид (CWE-367 нащадок CWE-362). Ознака вразливого місця: операція читає значення, ухвалює рішення й записує значення. Рятує атомарність, а не швидкість.
- Від клієнта приходить «що» і «скільки», а «почім» рахує сервер — ціна, підсумок, податок і знижка з клієнта не приймаються ніколи.
- Сценарій зловживання пишеться руками з вимог, а перевірка автоматизується — у трьох формах: інваріант, одночасність, порядок кроків.
Можливі питання
- Чому сканер не знаходить логічних вразливостей? Чекають механізм, а не оцінку: немає сигнатури, немає контексту, інструменти перевіряють валідні ситуації. Сильна відповідь додає, що лишається людині — обхід процесу, редагування параметрів, ескалація привілеїв.
- Чим TOCTOU відрізняється від гонки? Перевіряють точність: гонка — ширший клас (CWE-362), TOCTOU — її вид (CWE-367) із формою «перевірив → використав».
- Як перевірити ліміт «один купон на акаунт»? Очікують два тести: послідовний і паралельний — і пояснення, чому послідовного мало.
- Як тестувати багатокроковий процес оформлення замовлення? Дивляться, чи назве кандидат три спроби (пропуск, повтор, перестановка), відкат при обриві й повторну перевірку на редагуванні.
- Розробник каже, що вікно між перевіркою і записом — мілісекунди й це нереально. Ваша відповідь? Перевіряють, чи є аргумент, а не думка: десятки запитів у межах мілісекунди, виміряна 1 мс проти 4 мс у last-byte sync.
- Що таке abuse case і що з ним робити далі? Означення — use case зі злим наміром; відрізняє сильну відповідь продовження: сценарій має стати вимогою або критерієм приймання, інакше він нічого не змінює.
Джерела
Що таке вразливість бізнес-логіки
- OWASP Cheat Sheet — Business Logic Security — означення класу через задуманий процес і теза «сигнатури, за якою зіставляти, немає».
- OWASP WSTG — 4.10.0 Introduction to Business Logic — складність виявлення й руйнівність, причини мовчання інструментів, спорідненість із функціональним тестуванням, правила бізнес-функції як точка входу.
- OWASP Top 10:2025 — A06 Insecure Design — категорія «відсутній або неефективний дизайн контролю», дефекти бізнес-логіки в ній, межа дизайну й реалізації.
- RFC 4949 — Internet Security Glossary, Version 2 — не кожна атака вдається; успіх залежить від глибини вразливості, сили атаки й контрзаходів.
- ISTQB Glossary — колізія
attack(тестовий підхід) іsecurity attack(дія зловмисника).
Обхід workflow: правильні кроки в неправильному порядку
- OWASP WSTG — 4.10.6 Testing for the Circumvention of Work Flows — означення класу, вимога порядку й відкату породжених дій, обидва приклади, misuse case з вимог.
- OWASP Cheat Sheet — Business Logic Security — три перевірки багатокрокового процесу й очікувана відмова замість просування.
- OWASP Cheat Sheet — REST Security — чому окрема авторизація кожного ендпоінта не ловить порушень порядку; питання чеклиста «Preventing Out-of-Order API Execution».
- OWASP Top 10:2025 — Mishandling of Exceptional Conditions — нова категорія A10 і повний відкат обірваної транзакції як failing closed.
Підтвердження транзакції: другий фактор на операцію, а не на вхід
- OWASP Cheat Sheet — Transaction Authorization — означення, WYSIWYS, розрізнення входу й підтвердження операції, унікальність даних на операцію, серверний контроль, послідовність кроків, анулювання при зміні даних, фінальна брама, рестарт після невдалих спроб і TOCTOU у підтвердженні.
TOCTOU: вікно між перевіркою і використанням
- CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition — означення, три написання акроніма, батьківський CWE-362, умова безпекової значущості, демонстраційний приклад із
access(), спостережений приклад CVE-2015-1743, наслідки, чотири заходи, межі SAST. - OWASP Cheat Sheet — Business Logic Security — ознака «читає, вирішує, записує», перевірка балансу без блокування, наслідки в балах і купонах, зняття аргументу про вузьке вікно, атомарність і дефолтний , форма тесту на одночасність.
Single-packet attack: коли вирішують мілісекунди
- PortSwigger Research — Smashing the state machine: the true potential of web race conditions — авторське означення прийому, вимога HTTP/2, притримування
END_STREAMі роль алгоритму Нейгла, бенчмарк Мельбурн — Дублін, Turbo Intruder, блокування PHP за сесією й застереження про тайминг. - PortSwigger Web Security Academy — Race conditions (single-packet attack) — несумісність із HTTP/1, паралельні групи запитів у Burp Repeater 2023.9, затримка бекенду як причина нерівного таймингу.
Маніпуляція ціною, кількістю й купонами
- OWASP Cheat Sheet — Business Logic Security — перерахунок підсумку з клієнтської ціни, правило «тільки ідентифікатори й кількість», серверна валідація купона, перелік патернів із відʼємною кількістю й стекінгом промо.
- OWASP WSTG — 4.10.0 Introduction to Business Logic — сценарій повернення на сторінку підсумку в тій самій сесії.
- OWASP WSTG — 4.10.1 Test Business Logic Data Validation — вимога логічної валідності на обох боках, межа з аналізом граничних значень, недовіра до даних усередині системи, метод із перехоплювальним проксі й ціль «усе перевіряється на бекенді».
Патерни зловживання: перелік, проти якого проєктують
- OWASP Cheat Sheet — Business Logic Security — розділ про патерни, проти яких проєктують, і три форми автоматизованої перевірки: інваріант, одночасність, порядок кроків.
- OWASP WSTG — 4.10.6 Testing for the Circumvention of Work Flows — накрутка балів через скасування транзакції й контроль, що не повторюється на редагуванні.
- OWASP WSTG — 4.10.0 Introduction to Business Logic — межа автоматизації: сценарії зловживання лишаються ручною роботою, що спирається на знання процесу.
Анти-автоматизація: один контроль не тримає
- OWASP Cheat Sheet — Bot Management and Anti-Automation — каталог
OAT-001…OAT-021, ціль «підняти вартість», крихкість одного контролю й шари, honeypot-поле й тарпітинг. - OWASP Cheat Sheet Series — Authentication — CAPTCHA як контроль глибини захисту, а не запобіжник.
- OWASP WSTG — 4.4.3 Testing for Weak Lock Out Mechanism — CAPTCHA не заміняє механізм блокування.
- OWASP Cheat Sheet — Denial of Service — головоломки працюють проти зловживання функціональністю, але не проти DoS.
Abuse cases: сценарій зловживання, що став вимогою
- ISTQB Glossary — означення
abuse caseчерез акторів зі злим наміром. - OWASP Cheat Sheet — Abuse Case — архівний статус і його причина, означення через непередбачений спосіб використання функції, вимога перетворити сценарій на вимогу чи критерій приймання, унікальний ідентифікатор, роль автотестів і QA,
abuse of functionality. - OWASP WSTG — 4.10.6 Testing for the Circumvention of Work Flows — misuse case як метод: розробити сценарій і спробувати обійти кожен логічний потік.
Що таке вразливість бізнес-логіки і чим вона відрізняється від інʼєкції чи XSS?
Це дефект задуманого процесу, а не обробки вводу. При чи XSS застосунок робить те, чого від нього ніхто не просив: у дані підмішали команду або скрипт. У логічній вразливості код відпрацьовує рівно за написаним сценарієм — розходиться з потребою бізнесу сам сценарій. Тому тут немає пейлоада, який можна вставити в поле: ламається послідовність дій і правила процесу, а не парсер. OWASP описує клас як один із найважчих у виявленні й водночас найруйнівніших при експлуатації, до того ж зазвичай специфічний для конкретного застосунку. Для QA це і добра, і погана новина: технік вчити не треба, зате правила процесу доводиться знати краще за автора коду.
Чому сканер безпеки мовчить на логічних дефектах?
Бо шукати нема за чим. Інструмент зіставляє трафік і відповіді з відомими сигнатурами, а в логічної вразливості сигнатури не існує — той самий запит на переказ буває і легітимним, і шахрайським залежно від того, що сталося на попередньому кроці. Контексту процесу сканер не розуміє, тож перевіряє, що функція працює у валідних ситуаціях, і на цьому зупиняється. формулює межу різко: інструмент не має способу з'ясувати, чи вдасться користувачеві обійти потік процесу правкою параметрів, вгадуванням імен ресурсів чи підняттям привілеїв, і навіть запідозрити такий стан не здатен. Практичний висновок для співбесіди: зелений скан — не доказ, а лише відсутність доказу; сильна відповідь одразу називає, що саме лишається людині.
Куди логічні дефекти потрапляють у карті OWASP і чому знахідку не закривають фіксом у коді?
Їхній дім — категорія A06:2025 Insecure Design, тобто «відсутній або неефективний дизайн контролю»; дефекти бізнес-логіки названі в ній прямо, зокрема через брак опису небажаних і неочікуваних змін стану. OWASP навмисно тримає межу між дизайном і реалізацією: у них різна першопричина, різний момент появи й різне лікування. Ключова теза — навіть бездоганна реалізація не рятує , бо потрібного контролю в системі просто не проєктували, і писати його ідеально нема з чого. Тому логічна знахідка не закривається патчем в одному місці: вона повертає команду до обговорення процесу. На практиці це означає, що баг-репорт варто формулювати як «правило X ніде не примушується виконуватись», а не «на ендпоінті Y бракує перевірки».
Що таке обхід workflow і якими трьома спробами перевіряють багатокроковий процес?
(circumvention of work flows) — це можливість зловживати застосунком так, щоб пройти повз задуману послідовність кроків бізнес-процесу. Перевіряють його трьома однотипними спробами: пропустити крок, повторити крок і переставити кроки місцями. Очікуваний результат кожної спроби один і той самий — , а не просування процесу далі. Ключова деталь, яку часто забувають озвучити: перевіряти треба не лише статус відповіді, а й побічні ефекти, бо застосунок цілком може відповісти помилкою й водночас щось нарахувати. Готового чеклиста під конкретний продукт тут не буде — сценарії надто специфічні для системи, тому їх виводять із вимог і use case-ів руками.
Процес обірвався на середині — що має статися з тим, що вже нарахувалося?
Усе, що процес устиг зробити, і все, що з цього народилося, має відкотитися або скасуватися. Це друга половина вимоги до workflow, і забувають саме її: команди старанно перевіряють і порядок кроків, а обрив просто не тестують. Класичний приклад — відкривають, бали лояльності одразу лягають на рахунок, транзакцію скасовують, а рівень залишається накрученим без жодної покупки. У редакції Top 10 2025 обрив покриває нова категорія A10 Mishandling of Exceptional Conditions, і рецепт там збігається з WSTG: незавершену транзакцію треба скасувати частина за частиною й запускати з нуля, а не намагатися доліпити з середини — це і є failing closed. Для тесту звідси випливає проста форма: перервати процес, а тоді перевірити стан усіх залежних сутностей, а не тільки головної.
Контроль стоїть на створенні сутності. Що ще треба перевірити?
Те саме на редагуванні, скасуванні та будь-якому іншому переході стану. Показовий приклад із WSTG: модерація звіряє допис зі списком заборонених слів під час публікації, а на редагуванні його вже ніхто ні з чим не порівнює — і чистий допис після правки стає яким завгодно. Форма дефекту тут не «перевірки немає», а «перевірка стоїть не на всіх переходах», і саме цим він відрізняється від звичайного пропуску валідації. Практика для QA виглядає механічно: для кожного знайденого контролю випишіть повний перелік шляхів, якими сутність може змінитися, і пройдіть їх усі. Типова причина архітектурна: контроль реалізували в одному хендлері, а не в моделі чи сервісному шарі, тому альтернативний шлях його не бачить.
Чому окрема авторизація кожного API-ендпоінта не рятує від порушення порядку кроків?
Бо кожен відповідає на питання «чи має цей користувач право викликати саме мене», а не «чи має він право робити це зараз». Формально всі перевірки зелені: токен валідний, роль підходить, доступ до ресурсу є — просто крок викликано в момент, коли процес до нього ще не дійшов. OWASP прямо попереджає, що звичні перевірки доступу цей клас проблем зазвичай пропускають, і тримає під нього окремий перелік питань «Preventing Out-of-Order API Execution». Питання прикладні: чи можна викликати ендпоінти поза послідовністю, чи валідує кожен із них поточний стан процесу, чи придатні токени кроків до . У тесті це перевіряється дешево — пройти перший крок, стрибнути одразу на третій і переконатися, що сервер відмовив і нічого не створив.
Що таке маніпуляція ціною і яке одне правило знімає весь цей клас?
(price manipulation) — це коли сервер рахує підсумок із ціни, яку йому надіслав клієнт, замість того щоб узяти власну з бази. Наслідок відомий: телевізор за один цент, бо в тілі запиту поправили одне число. WSTG описує той самий сюжет через сторінку підсумку — користувач доходить до неї, повертається назад у тій самій валідній сесії, підставляє меншу ціну й спокійно завершує покупку. Правило, яке закриває клас цілком, коротке: від клієнта приймаються тільки ідентифікатори товарів і кількість, а ціна, проміжний підсумок, податок і загальна сума рахуються на сервері й ніколи не приймаються ззовні. Зі знижками так само: клієнт надсилає код купона, а чи діє він, на що діє і скільки віднімає — вирішує бекенд. на такий тест ставиться не на статус відповіді, а на суму в створеному замовленні.
Які ще кейси перевіряють у кошику, крім підміни ціни?
Стандартний набір із чотирьох. Відʼємна кількість — коли мінус у полі перетворюється на «повернення» коштів проти вартості іншого товару в тому самому кошику. Повторне застосування купона або застосування його до товарів, для яких він не призначався. Стекінг промо — кілька акцій, кожна з яких сама по собі законна, разом опускають ціну нижче собівартості. І одноразовий ваучер, погашений двома паралельними запитами, — це вже перетин із гонками, і послідовним тестом його не спіймати. Практично зручно тримати цей перелік як шаблон: він застосовний до будь-якого процесу, де є гроші, бали або ліміти, а не тільки до e-commerce.
Чим TOCTOU відрізняється від race condition?
— це вид гонки, а не її синонім, і на співбесіді перевіряють саме цю точність. У каталозі MITRE -367 (Time-of-check Time-of-use) стоїть нащадком CWE-362 «Concurrent Execution using Shared Resource with Improper Synchronization», тобто гонка — ширший клас, а TOCTOU — його впізнаваний окремий випадок із формою «перевірив → використав». Суть дефекту в тому, що продукт перевіряє стан ресурсу перед використанням, а стан устигає змінитися так, що результат перевірки вже не дійсний. Безпековою слабкістю це стає тоді, коли на стан ресурсу між перевіркою й використанням може вплинути . Дрібниця, яка рятує при пошуку в трекерах: акронім пишуть трьома способами, і варіант TOCCTOU MITRE вважає найімовірнішою одруківкою від TOCTTOU.
Як упізнати місце в застосунку, вразливе до гонки?
Ознака формулюється одним рядком: якщо операція спершу читає значення, потім вирішує щось на його підставі, а потім записує результат — вона вже потенційна гонка. Найпростіший приклад — ендпоінт, який дивиться баланс і списує кошти без блокування: два одночасні запити обидва бачать достатню суму й обидва списують. Наслідки з цієї механіки виростають упізнавані: з рахунків витікають бали лояльності, баланс провалюється в мінус, купон на одне використання спрацьовує кілька разів, а бонус «один на акаунт» перетворюється на «скільки встигнеш». Позиція за замовчуванням для тестувальника — вважати одночасність реальною : якщо два запити здатні змагатися, рано чи пізно вони змагатимуться. Тож на кожен такий ендпоінт пишеться тест, який навмисно їх зіштовхує, а не тільки послідовний happy path.
Розробник каже: «у нас же транзакція в базі, гонка неможлива». Що відповісти?
Що транзакція сама по собі не робить операцію атомарною. Більшість баз працюють за замовчуванням на такому , де прочитати стан і оновити його — дві розділені дії, а в шпарину між ними встигає зайти сусідня транзакція: обидві бачать один і той самий баланс і обидві його списують. Рятує не факт транзакції, а атомарність конкретної операції: блокування, взяте до перевірки, а не після, або одна умовна команда, що перевіряє й оновлює за раз. CWE серед заходів окремо називає й повторну перевірку ресурсу після використання, а скорочення проміжку між перевіркою й дією прямо позначає як паліатив — проблему воно не виправляє. І остання деталь для розмови: мовчання тут нічого не доводить, бо ловить лише частину таких випадків.
Як перевірити ліміт «один купон на акаунт»?
Двома тестами, і саме пояснення, чому одного мало, і відрізняє сильну відповідь. Перший — послідовний: погасити купон, потім спробувати ще раз і переконатися, що друга спроба відхилена. Він ловить банальну відсутність перевірки, але нічого не каже про одночасність. Другий — паралельний: вистрілити кількома однаковими запитами разом і перевірити, що успішним виявився рівно один, а стан у базі змінився один раз. Саме другий ловить класичну гонку, коли перевірка «купон уже використано» встигає відпрацювати для всіх запитів до того, як хоч один із них позначить його використаним. Асертити тут треба не тільки кількість успішних відповідей, а й підсумковий стан — буває, що сервер віддає помилку, а знижку вже нарахував.
«Вікно між перевіркою й записом — мілісекунди, це нереально проексплуатувати». Ваш аргумент?
Аргумент, а не думка: сучасні інструменти без зусиль укладають десятки запитів у межі однієї мілісекунди, тож якщо вікно існує взагалі, вважайте його експлуатованим. Історично так справді не було — мережевий джитер розтягував прибуття запитів, і вузькі вікна знаходити було надзвичайно важко. Прийом, який це прибрав, — від James Kettle (PortSwigger Research), показаний на Black Hat USA 2023: він змушує 20-30 запитів прибути на сервер одночасно незалежно від джитера. Цифра з авторського бенчмарку добре працює в суперечці: двадцять запитів гнали через пів планети, з Мельбурна до Дубліна, і медіанний розрив між початком обробки першого й останнього запиту в пачці склав 1 мс проти 4 мс у попереднього прийому last-byte sync, а полювання на реальну скоротилося з понад двох годин приблизно до 30 секунд. Тобто «нереально» перестало бути правдою кілька років тому.
На чому технічно тримається single-packet attack?
На HTTP/2: там кілька запитів їдуть конкурентно в межах одного зʼєднання, а HTTP/1.1 такого не вміє — його запити шикуються в чергу. Через це прийом із HTTP/1 просто несумісний, тому підтримку HTTP/2 на цілі перевіряють найпершою. Механіка така: від кожного запиту притримують крихітний фрагмент — заголовки йдуть без прапорця END_STREAM, а порожній DATA-фрейм із ним лишають наостанок. Потім усі 20-30 запитів «дозавершують» одним TCP-пакетом, і для цього TCP_NODELAY має бути вимкнений: фінальні фрейми збирає докупи саме алгоритм Нейгла. Руками це не роблять — прийом живе в розширенні Turbo Intruder для , а починаючи з Burp Suite 2023.9 і в самому Repeater через групи паралельних запитів. Знати механіку варто не заради реалізації, а щоб розуміти межі: без HTTP/2 результат тесту на гонку нічого не доводить.
Пачка запитів обробилася послідовно. Це означає, що гонки немає?
Ні, спершу треба виключити два банальні пояснення. Перше: серіалізувати могла не логіка застосунку, а платформа — PHP за замовчуванням блокує за ідентифікатором сесії, тож усі запити з однією сесією чемно шикуються в чергу; лікується це тим, що кожен запит іде зі своєю сесією. Друге: нерівний тайминг сам собою нічого не каже про застосунок — якщо навіть при одночасному прибутті стабільно різний, це вказує на на боці бекенду, а не на блокування в бізнес-логіці. Тому перед висновком «тут атомарно» варто переконатися, що запити справді доїхали разом і що умови рівні. Загальне правило те саме, що з порожнім результатом будь-якої спроби: невдала атака не доводить відсутності вразливості, бо її успіх залежить від глибини дефекту, сили самої спроби й дієвості контрзаходів.
Чим підтвердження транзакції відрізняється від входу в систему і що таке WYSIWYS?
(transaction authorization) — це другий фактор, поданий саме на чутливу операцію, а не на вхід: система перевіряє право користувача виконати конкретно цей переказ. Якщо вхід і підтвердження виглядають однаково, зʼявляється найдешевша атака: шкідливе ПЗ бере перші облікові дані для автентифікації, а другі використовує, щоб авторизувати шахрайську операцію. Принцип WYSIWYS (What You See Is What You Sign) вимагає іншого: людина має бачити перед підтвердженням саме ті дані, що визначають операцію, і свідомо погодитися з ними, а не з абстрактним фактом «щось відбувається». Тому голе «введіть код із SMS» без жодних деталей — це дефект, а не питання формулювань: повідомлення без змістовної підказки стає готовим інструментом для соціальної інженерії та шкідливого ПЗ. Далі перелік перетворюється на тести майже дослівно: авторизаційні дані унікальні для кожної операції, контроль на сервері, кроки не переставляються, зміна даних анулює вже введений OTP і сам , перед виконанням стоїть фінальна перевірка факту авторизації, а після кількох невдалих спроб процес починається наново, а не просто крутить лічильник.
Що таке abuse case, який у нього статус і що з ним робити далі?
За — це use case, у якому зі злим наміром шкодять системі або іншим акторам; є й друге означення, з боку функції: спосіб її використання, якого розробник не передбачав. Статус техніки варто знати: шпаргалка OWASP під неї архівна, і причину названо в ній самій — рецензенти зауважили, що на практиці abuse cases застосовують рідко, каркас видається важким, а опублікованих прикладів мало. Тож це описана техніка, а не чинна рекомендація; жива вона під іншою назвою — у WSTG той самий зміст носить misuse case, і там це не опція, а спосіб роботи: скласти сценарій і спробувати обійти кожен виявлений логічний потік. Найважливіше в відповіді — продовження: відібраний сценарій має стати вимогою безпеки, секцією специфікації або user story, інакше його ніхто не оцінить за вартістю й не реалізує. Кожному сценарію дають унікальний ідентифікатор, щоб трасувати впродовж проєкту, а автотести під нього ще й стежать, чи контрзахід лишився на місці після наступних виправлень. До речі, QA згаданий у переліку учасників поіменно: саме тестувальник тримає в голові і правильну поведінку функції, і неправильну, і те, від чого вона розсипається.
Що з тестування бізнес-логіки автоматизується, а що ні?
Межа проходить між сценарієм і перевіркою. Відбір сценаріїв зловживання автоматизувати неможливо: це ручна робота, яка спирається на знання повного бізнес-процесу та його правил, і саме тому клас лишається людським. А от щойно сценарій відібрано, перевірка автоматизується цілком, і OWASP називає три її форми. Перша — тест на інваріант: кожне правило, яке виявили під час , отримує перевірку, що червоніє при його порушенні. Друга — тест на одночасність. Третя — тест на порядок кроків, де очікуваним результатом є відмова. Практичний наслідок для команди: логічні перевірки не живуть окремим «безпековим» прогоном, вони стають частиною звичайної . І окремо про регресію контрзаходів — автотест під abuse case цінний саме тим, що ловить мовчазне зникнення захисту через півроку.
Тест на формі поводиться дивно: то падає, то ні, а один сценарій завжди відхиляється. Що варто виключити, перш ніж заводити баг?
Захист від автоматизації. Дві його форми маскуються під дефекти застосунку особливо вдало. — приховане поле форми, яке заповнюють лише боти; автотест, що чесно заповнює все підряд або підставляє значення напряму в DOM, потрапляє в нього й отримує відмову, схожу на баг валідації. — замість чесного 403 підозрілому клієнту дедалі довше не відповідають, і з боку прогону це виглядає рівно як із . Обидві поведінки свідомі, тож поводження автотестів із ними узгоджують наперед — виняток для тестового трафіку, окремий заголовок чи середовище. Ширший контекст той самий: один контроль крихкий, тому їх ставлять шарами, і запит, що виглядає людським на одному шарі (нормальний IP, розвʼязана ), провалюється на іншому — наприклад, коли за тридцять секунд проходить десять оформлень замовлення різними картками.
Три кейси, у яких застосунок відповідає без єдиної помилки, а бізнес втрачає гроші: карта спроб для багатокрокового чекауту, зіткнення паралельних запитів на одноразовому ваучері й розбір тіла запиту, куди клієнт кладе ціну. Скрізь — що робимо, що і як не переплутати захист із багом.
Кейс 1. Багатокроковий чекаут: три спроби і відкат
Процес складається з чотирьох кроків: кошик → доставка → оплата → підтвердження. Тестуванням тут займаються й без нас, тому беремо інший бік — те, що станеться, якщо кроки пройти не так, як задумано. Спроб обходу рівно три, і очікуваний результат у всіх трьох однаковий — , а не просування. Четвертий рядок таблиці спробою обходу не є: там процес обривають і дивляться на відкат.
| Спроба | Що робимо | Очікуваний результат | Що асертимо, крім статусу |
|---|---|---|---|
| Пропуск | Створюємо замовлення й одразу викликаємо оплату, обійшовши доставку | Відмова | Платежів за замовленням немає, статус замовлення не зрушив |
| Повтор | Двічі підтверджуємо той самий крок оплати | Відмова на повторному виклику | Списання рівно одне, замовлення не роздвоїлось |
| Перестановка | Підтверджуємо замовлення до оплати | Відмова | Замовлення не в статусі «підтверджено», лист клієнту не пішов |
| Обрив | Доходимо до оплати й скасовуємо процес | Відкат усього | Бали/резерв складу/чернетка рахунку зняті, а не «висять» |
Останній рядок — той, який зазвичай не тестують. Саме він ловить класику: відкрили, бали лояльності лягли на рахунок, транзакцію скасували — рівень користувача виріс без жодної покупки.
import { test, expect } from '@playwright/test';
test('оплата без пройденого кроку доставки: відмова і жодного побічного ефекту', async ({ request }) => {
const order = await createOrder(request, { sku: 'TV-55', qty: 1 }); // крок 1
// крок 2 (доставка) свідомо пропущено
const res = await request.post('/api/checkout/pay', {
data: { orderId: order.id, method: 'card' },
});
expect(res.ok(), 'сервер пустив оплату повз крок доставки').toBeFalsy();
// головне не тут, а нижче: відмова у відповіді ще не означає відмову в стані
const state = await (await request.get(`/api/orders/${order.id}`)).json();
expect(state.status).toBe('draft');
expect(state.payments).toHaveLength(0);
});
test('скасування процесу відкочує нараховані бали', async ({ request }) => {
const before = await loyaltyBalance(request);
const order = await createOrder(request, { sku: 'TV-55', qty: 1 });
await request.post('/api/checkout/cancel', { data: { orderId: order.id } });
expect(await loyaltyBalance(request)).toBe(before); // нічого не «прилипло»
});
Що дивитися і чому:
- Статус відповіді — найслабший асерт у цій темі. Застосунок часто повертає
400, а побічний ефект уже стався: резерв створено, бали нараховано, лист відправлено. Тому кожна спроба перевіряється двічі — за відповіддю і за станом усіх залежних сутностей. - Контроль, знайдений на створенні, перевіряється на редагуванні. Якщо валідація адреси доставки стоїть у хендлері створення, спробуйте змінити адресу на вже створеному замовленні: дуже часто перевірка живе в одному місці, а альтернативний шлях її не бачить.
- На API це підступніше, ніж у браузері. Кожен авторизується сам по собі й відповідає на питання «чи можна тобі мене викликати», а не «чи можна це зараз». Тому в тест варто додати питання з переліку «Preventing Out-of-Order API Execution»: чи придатний токен одного кроку до на іншому кроці процесу.
- Звідки брати сценарії. Універсального чеклиста під ваш продукт не існує — перелік вище виводиться з опису процесу. Робочий формат: випишіть правила бізнес-функції звичайним текстом, а тоді для кожного правила спитайте, чи змушує застосунок його виконувати.
Кейс 2. Одноразовий ваучер: зіштовхнути запити й правильно прочитати результат
Ліміт «один раз на акаунт» послідовним тестом перевіряється за півхвилини — і саме тому команда вважає його покритим. Насправді послідовний тест ловить лише повну відсутність перевірки. Гонку ловить інший: перевірка встигає відпрацювати для всіх запитів раніше, ніж хоч один із них позначить ваучер використаним.
import { test, expect, type APIRequestContext } from '@playwright/test';
test('послідовно: друге погашення відхилено', async ({ request }) => {
expect((await redeem(request)).ok()).toBeTruthy();
expect((await redeem(request)).ok()).toBeFalsy();
});
test('паралельно: успішним має бути рівно один запит', async ({ request }) => {
const attempts = Array.from({ length: 10 }, () => redeem(request));
const responses = await Promise.all(attempts);
const accepted = responses.filter((r) => r.ok());
expect(accepted, 'ваучер погашено більш ніж один раз').toHaveLength(1);
// і окремо — стан, бо сервер міг віддати помилку вже після нарахування знижки
const cart = await (await request.get('/api/cart')).json();
expect(cart.discountsApplied).toBe(1);
});
function redeem(request: APIRequestContext) {
return request.post('/api/cart/voucher', { data: { code: 'WELCOME-1' } });
}
Результат такого тесту читається не однозначно, тому таблиця інтерпретацій корисніша за сам асерт:
| Що побачили | Найімовірніша причина | Що робити далі |
|---|---|---|
| Успішних більше одного | Гонка підтверджена | Баг: перевірка й запис не атомарні |
| Успішний рівно один | Або атомарність, або запити не доїхали разом | Перевірити, чи справді була одночасність |
| Усі запити пройшли послідовно | Можливе блокування платформи, а не логіки | Дати кожному запиту власну сесію й повторити |
| Час відповідей стабільно різний | Схоже на затримку бекенду | Це не доказ серіалізації в бізнес-логіці |
Що дивитися і чому:
Promise.allшле з одного процесу, але доїжджають запити по-різному. Мережевий джитер розтягує прибуття на мілісекунди, і вузьке вікно ви просто не влучите. Коли зелений результат треба довести, а не припустити, беруть : він змушує 20-30 запитів прибути одночасно попри джитер, але вимагає HTTP/2 на цілі — з HTTP/1 прийом несумісний, тож підтримку перевіряють першою.- Зелений паралельний тест — слабкий доказ. Невдала спроба не свідчить про відсутність вразливості: результат залежить від глибини дефекту, сили самої спроби й контрзаходів. Перш ніж писати в тікеті «гонки немає», переконайтеся, що умови зіткнення були чесними.
- Аргумент «вікно надто вузьке» знімається цифрою. У бенчмарку автора прийому пачка з 20 запитів, надіслана за 17 000 км, дала медіанний розрив між початком обробки першого й останнього запиту 1 мс проти 4 мс у попереднього підходу, а полювання на реальну скоротилося з понад двох годин приблизно до 30 секунд.
- Той самий шаблон застосовний ширше за купони. Ознака місця, куди варто принести цей тест, формулюється рядком: операція читає значення, ухвалює за ним рішення й записує значення. Баланс, ліміт запрошень, бонус «один на акаунт», лічильник місць — усе це та сама форма.
Кейс 3. Ціна в тілі запиту: що взагалі має право приходити від клієнта
Відкриваємо перехоплювальний , кладемо товар у кошик і дивимося на POST оформлення. Ось тіло, у якому все виглядає нормально рівно доти, доки не спитати, навіщо серверу ці поля:
{
"cartId": "c-90211",
"items": [
{ "sku": "TV-55", "qty": 1, "unitPrice": 24999.00, "lineTotal": 24999.00 }
],
"coupon": { "code": "SPRING", "discount": 500.00 },
"tax": 4166.50,
"total": 28665.50
}
Розкладемо поля за одним питанням — чи має сервер вірити цьому значенню:
| Поле | Приймати від клієнта | Чому |
|---|---|---|
sku, qty | Так | Це і є вибір користувача — «що» і «скільки» |
unitPrice, lineTotal | Ні | Ціна береться з бази; прийнята ззовні — це телевізор за один цент |
coupon.code | Так | Клієнт називає код, не більше |
coupon.discount | Ні | Розмір знижки вирішує сервер після валідації коду |
tax, total | Ні | Похідні значення; сервер рахує їх сам і клієнту лише показує |
test('підмінена ціна ігнорується сервером', async ({ request }) => {
const res = await request.post('/api/checkout', {
data: {
cartId,
items: [{ sku: 'TV-55', qty: 1, unitPrice: 0.01, lineTotal: 0.01 }],
total: 0.01,
},
});
// прийнятною є будь-яка з двох поведінок: відмова або перерахунок
if (res.ok()) {
const order = await res.json();
expect(order.total).toBe(24999.0); // сервер узяв ціну з бази, а не з запиту
} else {
expect(res.status()).toBeGreaterThanOrEqual(400);
}
});
test('відʼємна кількість не перетворюється на повернення коштів', async ({ request }) => {
const res = await request.post('/api/cart/items', {
data: { sku: 'MOUSE-1', qty: -3 },
});
expect(res.ok()).toBeFalsy();
const cart = await (await request.get('/api/cart')).json();
expect(cart.total).toBeGreaterThanOrEqual(0);
});
Що дивитися і чому:
- Асерт ставиться на суму, а не на статус. Сервер цілком може відповісти
200і створити замовлення на один цент. Тому перевірка формулюється як інваріант: підсумок замовлення дорівнює ціні з каталогу, помноженій на кількість, мінус знижка, порахована сервером. - Валідація у формі нічого не гарантує. Логічна перевірка має стояти і на фронтенді, і безпосередньо на сервері: локальний контроль обходиться проксі й не працює на стиках із суміжними системами. І окреме правило, яке ламає багато архітектур: дані не стають довіреними від того, що вже потрапили всередину системи.
- Це не аналіз граничних значень. Тут перевіряють логічну валідність, а не діапазон: неіснуючий ідентифікатор товару, знижку на категорію, для якої купон не призначався, дві акції, що разом опускають ціну нижче собівартості. Здебільшого таке взагалі не перевіряється в точці вводу — доводиться звірятися з іншою системою.
- Формулювання цілі, яке варто винести в тікет. Не «додайте перевірку на ендпоінт», а «усі перевірки відбуваються на сервері й їх не можна обійти». Логічний дефект закривається рішенням про дизайн процесу, а не патчем в одному хендлері — саме тому категорія називається Insecure Design.
Що це за клас і чому його не бачить автоматика
- Можу пояснити різницю: чи XSS змушують застосунок робити незаплановане, а логічна — це коли код чесно виконує сценарій, що розходиться з потребою бізнесу.
- Знаю три причини мовчання сканера — немає сигнатури, немає розуміння контексту, перевіряється лише робота у валідних ситуаціях — і памʼятаю, що невдала спроба нічого не доводить.
- Розумію, чому знахідка живе в A06:2025 Insecure Design і чому патч в одному хендлері її не закриває: потрібного контролю в системі просто не проєктували.
Порядок кроків і обхід workflow
- Памʼятаю три спроби для будь-якого багатокрокового процесу — пропустити, повторити, переставити кроки — і що очікуваний результат кожної однаковий: , а не просування.
- Знаю другу половину вимоги, яку забувають: обірваний процес відкочує все, що встиг нарахувати, включно з породженими діями (це і є failing closed з A10:2025).
- Перевіряю кожен знайдений контроль на всіх переходах стану, а не лише на створенні: фільтр заборонених слів стоїть на публікації, а на редагуванні допис уже ніхто не звіряє.
- Розумію, чому окрема авторизація кожного API- не ловить порушення порядку, і знаю питання з переліку «Preventing Out-of-Order API Execution»: виклик поза послідовністю, валідація поточного стану, токенів між кроками.
Ціна, кількість і купони
- Знаю правило, що знімає весь клас : від клієнта приходять лише ідентифікатори товарів, кількість і код купона, а ціна, підсумок, податок і розмір знижки рахуються на сервері.
- Тримаю в голові стартовий набір кейсів кошика: відʼємна кількість, повторне застосування купона, купон не на ті товари, стекінг промо нижче собівартості, паралельне погашення одноразового ваучера.
- Можу пояснити, чому валідації лише на фронтенді мало: запит переписується , він не працює на стиках із суміжними системами, а дані не стають довіреними від того, що вже опинилися всередині системи — і памʼятаю, що це не аналіз граничних значень, бо звірятися часто доводиться з іншою системою.
TOCTOU і одночасність
- Не плутаю з гонкою: -367 — нащадок CWE-362, тобто гонка ширший клас, а TOCTOU — її вид із формою «перевірив → використав».
- Можу назвати ознаку вразливого місця одним рядком: операція спершу читає значення, потім вирішує щось на його підставі й записує результат — і памʼятаю, що мовчання тут нічого не гарантує.
- Маю готовий контраргумент на «у нас же транзакція в базі»: за замовчуванням розділяє читання стану й оновлення на дві дії, а між ними встигає зайти сусідня .
- Перевіряю кожен ліміт «один на акаунт» двічі — послідовно й паралельно — і не лише статус відповіді, а й підсумковий стан.
- Знаю, навіщо потрібен (мережевий джитер розтягує прибуття запитів), що він вимагає HTTP/2 і дає розкид близько 1 мс проти 4 мс у last-byte sync — а також що послідовна обробка пачки може виявитися блокуванням платформи за сесією, а нерівний тайминг — бекенду.
Підтвердження чутливої операції
- Розрізняю вхід у систему й та можу пояснити атаку, коли перші дані йдуть на автентифікацію, а другі — на авторизацію шахрайської операції.
- Знаю принцип WYSIWYS і вимоги, що прямо стають тестами: унікальні авторизаційні дані на кожну операцію, серверний контроль, незмінний порядок кроків, анулювання OTP і при зміні даних, рестарт усього процесу після кількох невдалих спроб.
Abuse cases, анти-автоматизація і робота AQA
- Знаю означення і його статус: шпаргалка OWASP архівна, живий синонім у — misuse case, а сам сценарій має стати вимогою безпеки або з унікальним ідентифікатором.
- Знаю межу автоматизації: відбір сценаріїв — ручна робота на знанні процесу, а перевірка автоматизується у трьох формах — інваріант, одночасність, порядок кроків.
- Розпізнаю захист, що маскується під дефекти: виглядає як баг валідації, — як із , а лишається контролем й блокування не заміняє.
Квіз
Перед стартом
- Питань: 16
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Що таке вразливість бізнес-логіки?