BOLA (Broken Object Level Authorization, раніше IDOR - Insecure Direct Object Reference) - API перевіряє, що користувач автентифікований, але не перевіряє, чи має він доступ саме до цього об'єкта.
// вразливо: будь-який автентифікований користувач отримає будь-яке замовлення
Route::get('/api/orders/{order}', fn (Order $order) => new OrderResource($order))
->middleware('auth:sanctum');
Зловмисник змінює /api/orders/1041 на /api/orders/1040 - і бачить чуже замовлення. Ідентифікатори в API на виду, перебрати їх - справа скрипта.
Чому це вразливість №1 у OWASP API Top 10:
- у кожному ендпойнті з ідентифікатором треба окремо не забути перевірку;
- автоматичні сканери її погано знаходять - технічно запит коректний;
- тести зазвичай перевіряють «власник бачить своє», а не «чужий не бачить».
Захист у Laravel:
1. Політики:
public function show(Order $order): OrderResource
{
$this->authorize('view', $order); // OrderPolicy::view
return new OrderResource($order);
}
// або на маршруті
->can('view', 'order');
2. Пошук через власника - чужий запис просто не знайдеться:
$order = $request->user()->orders()->findOrFail($id);
3. Вкладені маршрути з обмеженням: scopeBindings() - дочірній запис має належати батьківському.
4. Списки - теж об'єкти: GET /api/orders?user_id=5 не повинен повертати замовлення іншого користувача. Фільтр за власником - на сервері, а не з параметра.
Чого не робити:
- покладатися на непередбачувані ID (UUID замість автоінкременту). UUID ускладнює перебір, але не замінює перевірку: ідентифікатор може потрапити в URL, лог, лист;
- перевіряти лише в інтерфейсі (прихована кнопка) - API викликають напряму;
- довіряти ідентифікатору з тіла запиту (
"owner_id": 5) - власника визначають з автентифікації.
Тест на кожен ендпойнт з ідентифікатором:
it('forbids viewing another user\'s order', function () {
$order = Order::factory()->create();
Sanctum::actingAs(User::factory()->create());
$this->getJson("/api/orders/{$order->id}")->assertForbidden();
});
Відповідь 403 чи 404: 404 не підтверджує, що об'єкт існує, - для чутливих даних це краще.