Query Builder передає через плейсхолдери лише значення: аргументи `where()`, `whereIn()`, `insert()`, `update()`. Усе, що ви пишете рядком у `DB::raw()`, `whereRaw()`, `selectRaw()`, `orderByRaw()`, `havingRaw()`, `DB::select()` чи `DB::statement()`, іде в SQL як є, тому дані туди передають лише масивом біндінгів. Імена колонок і напрям сортування не біндяться взагалі, для них потрібен перелік дозволених значень.
Безпека для Middle: питання на співбесіду
6 питань рівня Middle з теми Безпека з розгорнутими відповідями, порадами та перевіркою.
IDOR (insecure direct object reference) виникає, коли застосунок знаходить обʼєкт за ідентифікатором із запиту і не перевіряє, чи належить він поточному користувачу. `auth` відповідає лише на питання «хто ви», тому для кожного обʼєкта потрібна окрема перевірка: шукати через звʼязок власника (`$user->resumes()->findOrFail($id)`), через policy або через `scopeBindings()` для вкладених маршрутів. UUID ускладнює перебір, але доступ не перевіряє.
Розширення, імʼя і MIME-тип із запиту задає клієнт, тож тип файлу визначають за вмістом (`finfo`, правило `mimes` у Laravel), імʼя і розширення генерують самі, а файл кладуть поза публічною текою, де вебсервер не виконає PHP. Віддають файли через контролер з перевіркою доступу або з окремого домену, а розмір обмежують на рівні nginx, PHP і валідації.
Фіксація сесії: зловмисник заздалегідь підсовує жертві відомий йому id сесії, жертва входить, і якщо id після входу не змінився, зловмисник уже в акаунті. Тому при кожній зміні рівня доступу id регенерують: `session_regenerate_id(true)` у PHP, а в Laravel 13 це робить сам `Auth::login()` і `Auth::attempt()`. При виході сесію інвалідовують (`invalidate()` і `regenerateToken()`), а викрадення cookie ускладнюють прапорцями `Secure`, `HttpOnly`, `SameSite` і префіксом `__Host-`.
Токен скидання генерують криптостійким генератором (`random_bytes()`, `Str::random()`), у базі зберігають лише його хеш, дають короткий термін дії і видаляють після використання. Порівнюють секрети через `hash_equals()`, бо `===` зупиняється на першому відмінному байті. Відповідь форми однакова для відомого і невідомого email, а час відповіді вирівнюють, щоб не можна було перевірити, хто зареєстрований.
Перебір закривають обмеженням частоти: у Laravel це `RateLimiter::for()` з ключем, що поєднує email і IP, плюс `throttle` middleware, який віддає 429 з `Retry-After`. Ліміт тримається в спільному кеші (Redis), а `$request->ip()` має бути справжньою адресою клієнта, тобто `TrustProxies` налаштований. Від credential stuffing ліміти допомагають мало, тому додають перевірку злитих паролів, 2FA і моніторинг, а замість постійного блокування акаунта використовують тимчасові затримки.