• Час читання ~12 хв
  • 20.01.2023

Політики безпеки вмісту (CSP) - це чудовий спосіб підвищити безпеку вашої програми Laravel. Вони дозволяють додавати до білого списку джерела сценаріїв, стилів та інших ресурсів, які можуть завантажувати ваші веб-сторінки. Це запобігає введенню зловмисником шкідливого коду у ваше подання (а отже, і браузери ваших користувачів) і може дати вам додаткову впевненість у тому, що сторонні ресурси, які ви використовуєте, є тим, що ви мали намір використовувати.

У цій статті ми розглянемо, що таке CSP і чого вони досягають. Потім ми розглянемо, як використовувати пакет spatie/laravel-csp, щоб додати CSP до вашої програми Laravel. Ми також коротко розглянемо деякі поради, щоб спростити додавання CSP до існуючої програми.

Що таке Політика безпеки вмісту?

Простіше кажучи, CSP - це лише набір правил, які зазвичай повертаються з вашого сервера до браузера клієнта через заголовок Content-Security-Policy у відповіді. Це дозволяє нам, як розробникам, визначити, які активи дозволено завантажувати браузеру.

В результаті цього це може дати нам впевненість у тому, що наші користувачі завантажують у свій браузер лише зображення, шрифти, стилі та сценарії, які ми вважаємо безпечними для їх завантаження та дозволили використання. Якщо браузер спробує завантажити недозволений актив, він буде заблокований.

Використання добре налаштованої політики безпеки вмісту може знизити ймовірність крадіжки даних користувача та інших шкідливих дій, що здійснюються за допомогою атак, таких як міжсайтові сценарії (XSS).

CSP можуть стати дуже складними (особливо у великих додатках), але вони є життєво важливою частиною безпеки будь-якої програми. Як реалізувати CSP в

Laravel

Як ми вже згадували, CSP - це всього лише набір правил, які повертаються з вашого сервера в браузер клієнта через заголовок у відповіді, або іноді визначаються як тег <meta> в HTML. Це означає, що є кілька способів застосувати CSP до своєї програми. Наприклад, ви можете визначити заголовки в конфігурації вашого сервера (наприклад, - Nginx). Однак це може бути громіздким і складним в управлінні, тому я вважаю, що замість цього легше керувати політикою на рівні додатків.

Як правило, найпростішим способом додавання поліса до програми Laravel є використання пакета spatie/laravel-csp. Отже, давайте подивимося, як ми можемо його використовувати, і різні варіанти, які він нам надає.

Встановлення

Щоб розпочати роботу з використанням пакета spatie / laravel-csp, нам спочатку потрібно буде встановити його через Composer за допомогою такої команди:

composer require spatie/laravel-csp

Після цього ми можемо опублікувати файл конфігурації пакета за допомогою такої команди:

php artisan vendor:publish --tag=csp-config

Запуск вищевказаної команди повинен був створити для вас

новий файл конфігурації / csp.php. Застосування політики до відповідей

Тепер, коли пакет встановлено, нам потрібно переконатися, що заголовок Content-Security-Policy додано до ваших відповідей HTTP. Є кілька різних способів, якими ви можете захотіти це зробити, залежно від вашої програми. Якщо ви хочете застосувати CSP до

всіх своїх веб-маршрутів, ви можете додати клас проміжного програмного забезпечення Spatie\Csp\AddCspHeaders до веб-частини масиву $middlewareGroups у вашому додатку / Http / Kernel.php файлі:В результаті цього будь-який маршрут, який проходить через вашу веб-групу

// ...
protected $middlewareGroups = [
   'web' => [
       // ...
       \Spatie\Csp\AddCspHeaders::class,
   ],
// ...

проміжного програмного забезпечення, матиме автоматично доданий заголовок CSP для вас.

Якщо ви віддаєте перевагу додавати CSP до окремих маршрутів або будь-яких груп маршрутів, ви можете натомість використовувати проміжне програмне забезпечення у своєму веб.php файлі. Наприклад, якщо ми хочемо застосувати проміжне програмне забезпечення лише до певного маршруту, ми можемо зробити щось на зразок:Або, якщо ми

use Spatie\Csp\AddCspHeaders;

Route::get('example-route', 'ExampleController')->middleware(AddCspHeaders::class);

хочемо застосувати проміжне програмне забезпечення до групи маршрутів, ми можемо зробити наступне:За замовчуванням,

use Spatie\Csp\AddCspHeaders;

Route::middleware(AddCspHeaders::class)->group(function () {
    // Routes go here...
});

якщо ви явно не визначаєте політику, яку слід використовувати з проміжним програмним забезпеченням, буде використана політика, визначена в ключі за замовчуванням вашого опублікованого файлу config/csp.php. Тому ви можете оновити це поле, якщо хочете використовувати власну політику за замовчуванням.

Можливо, у вас може бути кілька політик безпеки вмісту для вашої програми або веб-сайту. Наприклад, у вас може бути CSP для використання на загальнодоступних сторінках вашого сайту, а інший CSP для використання в закритих частинах вашого сайту. Це може бути пов'язано з тим, що ви використовуєте різні набори ресурсів (наприклад, сценарії, стилі та шрифти) у кожному з цих місць. Отже, якби ми хотіли чітко визначити політику

, яку слід використовувати для конкретного маршруту, ми могли б зробити наступне:

use App\Support\Csp\Policies\CustomPolicy;
use Spatie\Csp\AddCspHeaders;

Route::get('example-route', 'ExampleController')->middleware(AddCspHeaders::class.':'.CustomPolicy::class);

Аналогічно, ми також могли б чітко визначити політику в групі маршрутів:

use App\Support\Csp\Policies\CustomPolicy;
use Spatie\Csp\AddCspHeaders;

Route::middleware(AddCspHeaders::class.':'.CustomPolicy::class)->group(function () {
    // Routes go here...
});

Використовуючи політику безпеки вмісту за замовчуванням

Пакет поставляється з типовою політикою Spatie\Csp\Policies\Basic, яка визначає кілька правил вже для нас. Політика дозволяє нам завантажувати зображення, шрифти, стилі та сценарії лише з того самого домену, що й наш додаток. Якщо ви використовуєте лише об'єкти, завантажені з власного домену, цієї політики може бути достатньо для вас.

Базова політика створить заголовок Content-Security-Policy, який виглядає приблизно так:

ase-uri 'self';connect-src 'self';default-src 'self';form-action 'self';img-src 'self';media-src 'self';object-src 'none';script-src 'self' 'nonce-YKXiTcrg6o4DuumXQDxYRv9gHPlZng6z';style-src 'self' 'nonce-YKXiTcrg6o4DuumXQDxYRv9gHPlZng6z'

Створення власної політики безпеки вмісту

Залежно від вашої програми, ви можете створити власну політику, щоб дозволити завантажувати інші активи, дозволені Базовою політикою.

Як ми вже згадували, існує багато правил, які можна визначити в CSP, і вони можуть швидко стати відносно складними. Тож, щоб допомогти вам отримати коротке розуміння, ми розглянемо кілька загальних правил, які ви, ймовірно, будете використовувати у власній програмі.

Для цілей цього посібника ми збираємося зробити припущення, що у нас є проект, який використовує такі активи на сторінці:

  • Файл JavaScript, доступний у домені сайту за адресою: /js/app.js.
  • Файл JavaScript, доступний зовні за адресою: https://unpkg.com/vue@3/dist/vue.global.js.
  • Вбудований JavaScript - Але не будь-який вбудований JavaScript, ми хочемо дозволити лише вбудований JavaScript, який ми явно дозволили запускати.
  • Файл CSS, доступний у домені сайту за адресою: /css/app.css.
  • Файл CSS, доступний зовні за адресою: https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css
  • Зображення, доступне на домені сайту за адресою: /img/hero.png.
  • Зображення, доступне зовні за адресою: https://laravel.com/img/logotype.min.svg.

Ми створимо політику безпеки вмісту, яка дозволить завантажувати лише вищезазначені елементи на нашій сторінці. Якщо браузер спробує завантажити будь-який інший актив, запит буде заблоковано, і він не буде завантажений.

Базовий вигляд Blade для сторінки може виглядати приблизно так:

<html>
    <head>
        <title>CSP Test</title>

        {{-- Load Vue.js from the CDN --}}
        <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

        {{-- Load some JS scripts from our domain --}}
        <script src="{{ asset('js/app.js') }}"></script>

        {{-- Load Bootstrap 5 CSS --}}
        <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
              rel="stylesheet"
              integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD"
              crossorigin="anonymous"
        >
        {{-- Load a CSS file from our own domain --}}
        <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    </head>

    <body>
        <h1>Csp Header</h1>

        <img src="{{ asset('img/hero.png') }}" alt="CSP hero image">

        <img src="https://laravel.com/img/logotype.min.svg" alt="Laravel logo">

        {{-- Define some JS directly in our HTML. --}}
        <script>
            console.log('Loaded inline script!');
        </script>

        {{-- Evil JS script which we didn't write ourselves and was injected by another script! --}}
        <script>
            console.log('Injected malicious script! ☠️');
        </script>
    </body>
</html>

Для початку ми спочатку хочемо створити власний клас політики, який розширює клас Spatie\Csp\Policies\Basic. Немає спеціального каталогу, в якому вам потрібно це розмістити, тому ви можете вибрати де-небудь, що найкраще підходить для вашої програми. Мені подобається розміщувати своє в каталозі додатків/Підтримка/Csp/Політики, але це лише мої уподобання. Тому я створю новий додаток/Підтримка/Csp/Політики/CustomPolicy.php файл:

namespace App\Support\Csp\Policies;

use Spatie\Csp\Policies\Basic;

class CustomPolicy extends Basic
{
    public function configure()
    {
        parent::configure();

        // We can add our own policy directives here...
    }
}

Як ви можете бачити з коментаря в коді вище, ми можемо розмістити власні власні директиви в методі налаштування.

Отже, давайте додамо деякі директиви та подивимося, що вони роблять:

namespace App\Support\Csp\Policies;

use Spatie\Csp\Policies\Basic;

class CustomPolicy extends Basic
{
    public function configure()
    {
        parent::configure();

        $this->addDirective(Directive::SCRIPT, ['https://unpkg.com/vue@3/'])
            ->addDirective(Directive::STYLE, ['https://cdn.jsdelivr.net/npm/[email protected]/'])
            ->addDirective(Directive::IMG, 'https://laravel.com');
    }
}

Наведена вище політика створить заголовок Content-Security-Policy, який виглядає приблизно так:

ase-uri 'self';connect-src 'self';default-src 'self';form-action 'self';img-src 'self';media-src 'self';object-src 'none';script-src 'self' 'nonce-3fvDDho6nNJ3xXPcK3VMsgBWjVTJzijk' https://unpkg.com/vue@3/;style-src 'self' 'nonce-3fvDDho6nNJ3xXPcK3VMsgBWjVTJzijk' https://cdn.jsdelivr.net/npm/[email protected]/

У нашому прикладі вище ми визначили, що будь-який JS-файл, який завантажується з URL-адреси, яка починається з https://unpkg.com/vue@3/, може бути завантажений. Це означає, що наш скрипт Vue.js зможе завантажуватися, як і очікувалося.

Ми також дозволили завантажувати будь-який файл CSS, який завантажується з URL-адреси, яка починається з https://cdn.jsdelivr.net/npm/[email protected]/.

Крім того, ми також дозволили завантажувати будь-яке зображення, отримане з URL-адреси, яка починається з https://laravel.com.

Вам також може бути цікаво, де директиви дозволяють запускати вбудовані JavaScript і завантажувати зображення, CSS та JS файли з нашого домену. Всі вони включені в базову політику, тому нам не потрібно додавати їх самостійно. Таким чином, ми можемо зберегти нашу CustomPolicy гарною та бережливою, і додавати лише ті директиви, які нам потрібні (як правило, для зовнішніх активів).

Однак на даний момент, якби ми спробували запустити наш вбудований JavaScript, це не спрацювало б. Ми розглянемо, як це виправити далі.

Хоча наведені вище правила працюють і дозволять нашій сторінці завантажуватися належним чином, можливо, ви захочете зробити правила суворішими, щоб ще більше підвищити безпеку сторінки.

Уявімо, що з будь-якої невідомої причини шкідливому скрипту вдалося пробитися до URL-адреси, яка починається з https://unpkg.com/vue@3/, наприклад https://unpkg.com/vue@3/malicious-script.js. У зв'язку з поточною конфігурацією наших правил, цей скрипт буде дозволено запускати на нашій сторінці. Тому замість цього ми можемо захотіти чітко визначити точну URL-адресу сценарію, який ми хочемо дозволити завантажувати.

Ми оновимо нашу політику, щоб включити точні URL-адреси сценаріїв, стилів та зображень, які ми хочемо завантажити:

namespace App\Support\Csp\Policies;

use Spatie\Csp\Policies\Basic;

class CustomPolicy extends Basic
{
    public function configure()
    {
        parent::configure();

        $this->addDirective(Directive::SCRIPT, ['https://unpkg.com/vue@3/dist/vue.global.js'])
            ->addDirective(Directive::STYLE, ['https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css'])
            ->addDirective(Directive::IMG, 'https://laravel.com/img/logotype.min.svg');
    }
}

Наведена вище політика створить заголовок Content-Security-Policy, який виглядає приблизно так:

ase-uri 'self';connect-src 'self';default-src 'self';form-action 'self';img-src 'self' https://laravel.com/img/logotype.min.svg;media-src 'self';object-src 'none';script-src 'self' 'nonce-20gXfzoeWpjyg1ryUkWAma5gMWNN03xH' https://unpkg.com/vue@3/dist/vue.global.js;style-src 'self' 'nonce-20gXfzoeWpjyg1ryUkWAma5gMWNN03xH' https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css

Використовуючи цей підхід вище, ми можемо значно покращити безпеку нашої сторінки, оскільки тепер ми дозволяємо лише точні сценарії, стилі, і зображення, які ми хочемо завантажити.

Однак, як ви можете собі уявити, робити це для великих проектів може стати виснажливим і трудомістким, оскільки вам потрібно буде визначити кожен окремий актив, який ви завантажуєте із зовнішнього джерела. Отже, це те, що вам потрібно буде розглянути на основі проекту за проектом.

Додавання Nonces до вашого CSP Тепер, коли ми розглянули, як ми можемо дозволити

завантаження зовнішніх активів, нам також потрібно поглянути на те, як ми можемо дозволити запуск вбудованих сценаріїв.

Ви можете нагадати, що у нас було два вбудовані блоки сценаріїв у нашому перегляді Blade вище:Один

  • , який завантажив JS, який ми мали намір запустити
  • Один, який був введений шкідливим сценарієм і запустив якийсь злий код!

Сценарій був доданий в нижню частину вигляду Blade, виглядав так:

<html>
        <!-- ... -->

        {{-- Define some JS directly in our HTML. --}}
        <script>
            console.log('Loaded inline script!');
        </script>

        {{-- Evil JS script which we didn't write ourselves and was injected by another script! --}}
        <script>
            console.log('Injected malicious script! ☠️');
        </script>
    </body>
</html>

Щоб дозволити запуск вбудованих сценаріїв, ми можемо використовувати "nonces". Nonce - це випадковий рядок, який генерується для кожного запиту. Потім цей рядок додається до заголовка CSP (додається через базову політику, яку ми розширюємо), і будь-який вбудований скрипт, який завантажується, повинен включати цей nonce у свій атрибут nonce.

Давайте оновимо наш вигляд Blade, щоб включити nonce для нашого безпечного вбудованого сценарію, використовуючи помічник csp_nonce(), наданий пакетом:

<html>
        <!-- ... -->

        {{-- Define some JS directly in our HTML. --}}
        <script nonce="{{ csp_nonce() }}">
            console.log('Loaded inline script!');
        </script>

        {{-- Evil JS script which we didn't write ourselves and was injected by another script! --}}
        <script>
            console.log('Injected malicious script! ☠️');
        </script>
    </body>
</html>

В результаті цього наш безпечний вбудований скрипт тепер буде запущений, як і очікувалося. Тоді як введений скрипт, який не має атрибута nonce, буде заблокований для запуску.

Використання мета-тегу

Малоймовірно, але можливо, ви можете виявити, що вміст вашого заголовка Content-Security-Policy перевищує максимально дозволену довжину. Якщо це так, ми можемо додати мета-тег на нашу сторінку, який замість цього виводить правила для нашого браузера.

Для цього ви можете додати директиву пакета @cspMetaTag Blade до тегу <head> вашого перегляду, як так:

<html>
    <head>
        <!-- ... -->

        @cspMetaTag(App\Support\Csp\Policies\CustomPolicy::class)
    </head>

    <!-- ... -->

</html>

На прикладі нашої CustomPolicy вище це виведе наступний мета-тег:

<meta http-equiv="Content-Security-Policy" content="base-uri 'self';connect-src 'self';default-src 'self';form-action 'self';img-src 'self' https://laravel.com/img/logotype.min.svg;media-src 'self';object-src 'none';script-src 'self' 'nonce-oLbaz3rNhqvzKooMU8KpnqxgO9bFG1XQ' https://unpkg.com/vue@3/dist/vue.global.js;style-src 'self' 'nonce-oLbaz3rNhqvzKooMU8KpnqxgO9bFG1XQ' https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">

Поради щодо впровадження CSP в існуючій програмі Laravel

Додавання CSP до існуючої програми іноді може бути досить складним завданням. Дуже легко зламати свій користувальницький інтерфейс, запровадивши занадто суворий CSP або забувши додати правило для певного активу, яке може використовуватися лише на одній сторінці. Я підніму руку вгору і визнаю, що раніше робив це сам.

Тому, якщо ви отримаєте можливість впровадити CSP під час першого запуску нової програми, я настійно рекомендую це зробити. Набагато простіше написати політику разом зі створенням програми. Існує менша ймовірність того, що ви забудете додати певні правила, і ви навіть можете додати правила політики в тому ж git comit, що і ви додали актив, щоб ви могли легко відстежувати його в майбутньому.

Однак, якщо ви додаєте CSP до існуючої програми, є кілька речей, які ви можете зробити, щоб полегшити процес для себе та своїх користувачів.

По-перше, ви можете ввімкнути режим "лише звітувати" для своєї політики. Це дозволяє вам визначити свою політику, але щоразу, коли будь-яке з правил порушується (наприклад, завантаження активу, який не було дозволено завантажувати), звіт надсилатиметься на певну URL-адресу, а не блокуватиме завантаження активу. Роблячи це, він дозволяє створити CSP, який ви хотіли б використовувати, і протестувати його у своєму виробничому середовищі, не порушуючи вашу програму для користувачів. Потім ви можете використовувати звіти для виявлення будь-яких активів, які ви пропустили, і додавання їх до своєї політики.

Щоб увімкнути звітування про свою політику, спочатку потрібно встановити URL-адресу, на яку слід надсилати запит у разі виявлення порушення. Ви можете додати це, встановивши поле CSP_REPORT_URI у вашому файлі .env, як так:

CSP_REPORT_URI=https://example.com/report-sent-here

Потім ви можете використовувати метод reportOnly у своїй політиці. Якби ми оновили нашу політику, щоб повідомляти лише про порушення, це виглядало б так:

namespace App\Support\Csp\Policies;

use Spatie\Csp\Policies\Basic;

class CustomPolicy extends Basic
{
    public function configure()
    {
        parent::configure();

        $this->addDirective(Directive::SCRIPT, ['https://unpkg.com/vue@3/dist/vue.global.js'])
            ->addDirective(Directive::STYLE, ['https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css'])
            ->addDirective(Directive::IMG, 'https://laravel.com/img/logotype.min.svg')
            ->reportOnly();
    }
}

В результаті використання методу reportOnly до відповіді буде додано заголовок "Лише для вмісту-безпеки-Політики-Звіту" замість заголовка "Контент-Безпека-Політика". Наведена вище політика створить заголовок, який виглядає так:

eport-uri https://example.com/report-sent-here;base-uri 'self';connect-src 'self';default-src 'self';form-action 'self';img-src 'self' https://laravel.com/img/logotype.min.svg;media-src 'self';object-src 'none';script-src 'self' 'nonce-hI66wwieLS9inQh9GO4iaItVTFoPcNnj' https://unpkg.com/vue@3/dist/vue.global.js;style-src 'self' 'nonce-hI66wwieLS9inQh9GO4iaItVTFoPcNnj' https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css

Через певний проміжок часу (можливо, кілька днів, тижнів або місяців), якщо ви не отримали жодних звітів і у вас є впевненість, що політика підходить, ви можете ввімкнути її. Це означає, що ви все одно зможете отримувати звіти про порушення, але ви також зможете отримати всі переваги безпеки від впровадження політики, оскільки будь-які порушення будуть заблоковані. Для цього ви можете видалити reportOnly method call з вашого класу

політики.Крім того, вам також може бути корисно поступово підвищувати суворість ваших правил, як ми розглянули раніше в цій статті. Тому, можливо, ви захочете використовувати лише домени або символи узагальнення у вашому початковому CSP, а потім поступово змінювати правила, щоб використовувати більш конкретні URL-адреси.

Загалом, я думаю, що ключ до прийняття використання CSP у вашій існуючій програмі полягає в поступовому підході до нього. Безумовно, можна додати все це за один раз, але ви можете зменшити ймовірність помилок і помилок, застосувавши більш поступовий підхід.

Висновок

Сподіваємось, ця стаття повинна була дати вам огляд CSP, проблем, які вони вирішують, і як вони працюють. Ви також повинні тепер знати, як реалізувати CSP у власному додатку Laravel за допомогою пакета spatie/laravel-csp.

Ви також можете ознайомитися з документацією MDN щодо CSP, яка пояснює більше варіантів, доступних для використання у ваших програмах.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297