SSTI (server-side template injection) - дані користувача потрапляють у сам шаблон, а не в змінну, яку шаблон виводить. Рушій шаблонів компілює їх як код.
// вразливо: текст від користувача стає шаблоном
return Blade::render($request->input('greeting'), ['user' => $user]);
// greeting = "{{ system('cat .env') }}" - або @php ... @endphp
Blade компілюється в PHP, тож SSTI в Blade - це виконання довільного PHP-коду на сервері.
Різниця, яку треба розуміти:
// безпечно: шаблон - ваш, дані - змінна, екранована {{ }}
return view('emails.welcome', ['greeting' => $request->input('greeting')]);
// небезпечно: дані користувача - частина шаблону
Blade::render($userTemplate, $data);
Де таке трапляється на практиці:
- «шаблони листів, які редагує адміністратор» в адмінці - якщо адмінку зламано чи редакторів багато, це шлях до виконання коду на сервері;
- конструктори сторінок і повідомлень для клієнтів (SaaS: «налаштуйте текст сповіщення з плейсхолдерами»);
- генерація PDF чи документів з шаблонів, що зберігаються в базі;
- Twig/Smarty/Mustache у сторонніх пакетах - кожен рушій має свої способи «вирватися» з пісочниці.
Як робити безпечно:
- користувацьким шаблонам - лише заміна плейсхолдерів, а не повноцінний рушій:
$text = strtr($template, [
'{name}' => e($user->name),
'{order}' => e($order->number),
]);
- якщо потрібна логіка (умови, цикли) - рушій з пісочницею і білим списком дозволених функцій (наприклад, Twig Sandbox), обмежений набір змінних, без доступу до об'єктів з методами;
- Markdown замість HTML/Blade для контенту від користувачів - і рендер із санітизацією;
- ніколи
Blade::render,eval,create_function-подібних механізмів з даними ззовні.
Схоже на SSTI на клієнті: вираз {{ }} у даних, які Vue чи Angular компілюють як шаблон на сторінці (Vue змонтований на розмітку з користувацьким вмістом), - client-side template injection, що дає XSS.
Докладніше в документації: PortSwigger: Server-side template injection