Відкритий редирект - сайт перенаправляє на адресу з параметра запиту, не перевіряючи її:
// вразливо
return redirect($request->query('return_to'));
https://bank.example/login?return_to=https://bank-example.attacker.io/login
Посилання веде на справжній домен банку - користувач і поштовий фільтр йому довіряють. Після входу (чи одразу) людину перекидає на фішингову копію, яка просить «повторно ввести пароль» чи дані картки.
Чим ще небезпечно:
- крадіжка токенів в OAuth: якщо
redirect_uriчи проміжний редирект відкритий, код авторизації чи токен може піти на сайт нападника; - обхід перевірок «дозволених доменів» в інших системах, що довіряють вашому домену;
- SSRF, якщо сервер сам переходить за такими редиректами.
Як повертати користувача безпечно:
1. Лише відносні шляхи свого сайту:
$target = $request->query('return_to', '/');
$isLocalPath = str_starts_with($target, '/')
&& ! str_starts_with($target, '//')
&& ! str_contains($target, '\\');
return redirect($isLocalPath ? $target : '/');
//attacker.io - це протокол-відносний URL на чужий домен, а деякі браузери трактують /\attacker.io так само. Тому перевірка лише на початковий / недостатня.
2. Дозволений список - якщо потрібні переходи на інші домени (піддомени, партнери): порівнювати розібраний хост (parse_url) з переліком, а не шукати підрядок (str_contains($url, 'example.com') пропустить example.com.attacker.io).
3. Не передавати URL у параметрі взагалі: зберігати ціль у сесії. Саме так працює Laravel: middleware auth запам'ятовує запитану сторінку (url.intended), а після входу redirect()->intended('/dashboard') повертає туди - URL не проходить через параметр, який може змінити нападник.
4. Ідентифікатори замість адрес: ?next=orders з мапою на маршрути замість довільного URL.
Проміжна сторінка «Ви переходите на зовнішній сайт ...» - компроміс, якщо зовнішні переходи справді потрібні (наприклад, посилання в повідомленнях користувачів).