Meningkatkan Keamanan Aplikasi Laravel Anda Menggunakan CSP

SHARE :

Apa itu Content Security Policy?

Dalam istilah yang paling sederhana, CSP hanyalah sekumpulan aturan yang biasanya dikembalikan dari server Anda ke browser klien melalui header Content-Security-Policy sebagai respons.

Ini memungkinkan kita, sebagai pengembang, untuk menentukan aset apa yang boleh dimuat oleh browser.

Sebagai akibatnya, ini dapat memberi kami keyakinan bahwa pengguna kami hanya memuat gambar, font, gaya, dan skrip ke browser mereka yang kami anggap aman untuk mereka muat dan izinkan penggunaannya.

Jika browser mencoba memuat aset yang tidak diizinkan, aset tersebut akan diblokir.

Menggunakan kebijakan keamanan konten yang terkonfigurasi dengan baik dapat mengurangi kemungkinan pencurian data pengguna dan tindakan berbahaya lainnya yang dilakukan menggunakan serangan seperti Cross-Site Scripting (XSS).

CSP dapat menjadi sangat kompleks (terutama dalam aplikasi yang lebih besar), tetapi CSP merupakan bagian penting dari keamanan aplikasi apa pun.

Cara Menerapkan CSP di Laravel

Seperti yang telah kami sebutkan, CSP hanyalah sekumpulan aturan yang dikembalikan dari server Anda ke browser klien melalui header dalam respons, atau terkadang didefinisikan sebagai tag <meta> dalam HTML.

Artinya, ada beberapa cara untuk menerapkan CSP ke aplikasi Anda. Misalnya, Anda dapat menentukan header di konfigurasi server Anda (mis. - Nginx). Namun, ini bisa menjadi rumit dan sulit untuk dikelola, jadi menurut saya lebih mudah untuk mengelola kebijakan di tingkat aplikasi.

Biasanya, cara termudah untuk menambahkan kebijakan ke aplikasi Laravel Anda adalah dengan menggunakan paket spatie/laravel-csp.

Jadi mari kita lihat bagaimana kita bisa menggunakannya, dan opsi berbeda yang diberikannya.

Instalasi

Untuk memulai menggunakan paket spatie/laravel-csp, pertama-tama kita harus menginstalnya melalui Composer menggunakan perintah berikut :

composer require spatie/laravel-csp

Setelah itu, kita dapat menerbitkan file konfigurasi paket menggunakan perintah berikut :

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

Menjalankan perintah di atas seharusnya membuat file config/csp.php baru untuk Anda.

Menerapkan Kebijakan ke Respons

Sekarang setelah paket terinstal, kita perlu memastikan header Content-Security-Policy ditambahkan ke respons HTTP Anda. Ada beberapa cara berbeda yang mungkin ingin Anda lakukan, bergantung pada aplikasi Anda.

Jika Anda ingin menerapkan CSP ke semua rute web Anda, Anda bisa menambahkan kelas middleware Spatie\Csp\AddCspHeaders ke komponen web array $middlewareGroups di file app/Http/Kernel.php Anda :

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

Sebagai hasil dari melakukan ini, rute apa pun yang berjalan melalui grup middleware web Anda, akan menambahkan header CSP secara otomatis untuk Anda.

Jika Anda lebih suka menambahkan CSP ke rute individu atau grup rute apa pun, Anda dapat menggunakan middleware di file web.php Anda.

Misalnya, jika kami hanya ingin menerapkan middleware ke rute tertentu, kami dapat melakukan sesuatu seperti :

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

Atau, jika kami ingin menerapkan middleware ke grup rute, kami dapat melakukan hal berikut :

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

Secara default, jika Anda tidak secara eksplisit menentukan kebijakan yang harus digunakan dengan middleware, kebijakan yang ditentukan dalam kunci default dari file config/csp.php yang dipublikasikan akan digunakan.

Jadi, Anda mungkin ingin memperbarui kolom tersebut jika ingin menggunakan kebijakan default Anda sendiri.

Anda mungkin memiliki beberapa kebijakan keamanan konten untuk aplikasi atau situs web Anda.

Misalnya, Anda mungkin memiliki CSP untuk digunakan di halaman publik situs Anda, dan CSP lain untuk digunakan di bagian yang terjaga keamanannya di situs Anda.

Ini mungkin karena Anda menggunakan kumpulan aset yang berbeda (seperti skrip, gaya, dan font) di setiap tempat ini.

Jadi jika kita ingin secara eksplisit menentukan kebijakan yang harus digunakan untuk rute tertentu, kita dapat melakukan hal berikut :

use App\Support\Csp\Policies\CustomPolicy;
use Spatie\Csp\AddCspHeaders;
 
Route::get('example-route', 'ExampleController')->middleware(AddCspHeaders::class.':'.CustomPolicy::class);

Demikian pula, kami juga dapat secara eksplisit menentukan kebijakan dalam grup rute :

use App\Support\Csp\Policies\CustomPolicy;
use Spatie\Csp\AddCspHeaders;
 
Route::middleware(AddCspHeaders::class.':'.CustomPolicy::class)->group(function () {
    // Routes go here...
});

Menggunakan Kebijakan Keamanan Konten Default

Paket dikirimkan dengan kebijakan default Spatie\Csp\Policies\Basic yang sudah menetapkan beberapa aturan untuk kita.

Kebijakan tersebut hanya mengizinkan kami memuat gambar, font, gaya, dan skrip dari domain yang sama dengan aplikasi kami.

Jika Anda hanya menggunakan aset yang diambil dari domain Anda sendiri, kebijakan ini mungkin cukup untuk Anda.

Kebijakan Dasar akan membuat header Content-Security-Policy yang terlihat seperti ini :

base-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'

Membuat Kebijakan Keamanan Konten Anda Sendiri

Bergantung pada aplikasi Anda, Anda mungkin ingin membuat kebijakan Anda sendiri untuk mengizinkan pemuatan aset lain yang diizinkan oleh kebijakan Dasar.

Seperti yang telah kami sebutkan, ada banyak aturan yang dapat didefinisikan dalam CSP, dan aturan tersebut dapat menjadi relatif kompleks dengan cepat. Jadi untuk membantu Anda mendapatkan pemahaman singkat, kami akan melihat beberapa aturan umum yang mungkin akan Anda gunakan dalam aplikasi Anda sendiri.

Untuk tujuan panduan ini, kami akan membuat asumsi bahwa kami memiliki proyek yang menggunakan aset berikut di halaman :

  • File JavaScript tersedia di domain situs di: /js/app.js.
  • File JavaScript tersedia secara eksternal di: https://unpkg.com/vue@3/dist/vue.global.js.
  • JavaScript sebaris - Namun bukan sembarang JavaScript sebaris, kami hanya ingin mengizinkan JavaScript sebaris yang telah kami izinkan secara eksplisit untuk dijalankan.
  • File CSS tersedia di domain situs di: /css/app.css.
  • File CSS tersedia secara eksternal di: https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css
  • Gambar tersedia di domain situs di: /img/hero.png.
  • Gambar tersedia secara eksternal di: https://laravel.com/img/logotype.min.svg.

Kami akan membuat kebijakan keamanan konten yang hanya mengizinkan pemuatan item di atas di halaman kami. Jika browser mencoba memuat aset lain, permintaan akan diblokir dan tidak akan dimuat.

Tampilan Blade dasar untuk halaman mungkin terlihat seperti ini :

<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/bootstrap@5.3.0-alpha1/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>

Untuk memulai, pertama-tama kita ingin membuat kelas kebijakan kita sendiri yang memperluas kelas paket Spatie\Csp\Policies\Basic.

Tidak ada direktori tertentu yang Anda perlukan untuk menempatkannya, sehingga Anda dapat memilih tempat yang paling cocok untuk aplikasi Anda.

Saya suka menempatkan milik saya di direktori app/Support/Csp/Policies, tapi itu hanya preferensi saya. Jadi saya akan membuat file app/Support/Csp/Policies/CustomPolicy.php baru :

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...
    }
}

Seperti yang Anda lihat dari komentar pada kode di atas, kita dapat menempatkan arahan khusus kita sendiri dalam metode konfigurasi.

Jadi mari tambahkan beberapa arahan dan lihat apa yang mereka lakukan :

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/bootstrap@5.3.0-alpha1/'])
            ->addDirective(Directive::IMG, 'https://laravel.com');
    }
}

Kebijakan di atas akan membuat header Content-Security-Policy yang terlihat seperti ini :

base-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/bootstrap@5.3.0-alpha1/

Dalam contoh kami di atas, kami telah menetapkan bahwa file JS apa pun yang dimuat dari URL yang dimulai dengan https://unpkg.com/vue@3/ dapat dimuat. Ini berarti skrip Vue.js kita dapat dimuat seperti yang diharapkan.

Kami juga mengizinkan file CSS apa pun yang dimuat dari URL yang dimulai dengan https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/ untuk dimuat.

Selain itu, kami juga mengizinkan gambar apa pun yang diambil dari URL yang dimulai dengan https://laravel.com untuk dimuat.

Anda mungkin juga bertanya-tanya di mana arahan untuk memungkinkan JavaScript sebaris dijalankan, dan file gambar, CSS, dan JS dimuat dari domain kami.

Ini semua termasuk dalam kebijakan Dasar, jadi kita tidak perlu menambahkannya sendiri. Jadi kita dapat menjaga CustomPolicy kita tetap bagus dan ramping, dan hanya menambahkan arahan yang kita perlukan (biasanya untuk aset eksternal).

Namun, saat ini, jika kami mencoba menjalankan JavaScript sebaris kami, itu tidak akan berhasil. Kami akan membahas cara memperbaikinya lebih lanjut.

Meskipun aturan di atas berfungsi dan akan memungkinkan halaman kami dimuat seperti yang diharapkan, Anda mungkin ingin membuat aturan lebih ketat untuk lebih meningkatkan keamanan halaman.

Bayangkan, untuk alasan yang tidak diketahui, skrip berbahaya berhasil membuka URL yang dimulai dengan https://unpkg.com/vue@3/, seperti https://unpkg.com/vue@3/ skrip-berbahaya.js.

Karena konfigurasi aturan kami saat ini, skrip ini akan diizinkan untuk dijalankan di halaman kami. Jadi sebagai gantinya, kita mungkin ingin secara eksplisit menentukan URL persis dari skrip yang ingin kita izinkan untuk dimuat.

Kami akan memperbarui kebijakan kami untuk menyertakan URL persis dari skrip, gaya, dan gambar yang ingin kami muat :

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/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css'])
            ->addDirective(Directive::IMG, 'https://laravel.com/img/logotype.min.svg');
    }
}

Kebijakan di atas akan membuat header Content-Security-Policy yang terlihat seperti ini :

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-20gXfzoeWpjyg1ryUkWAma5gMWNN03xH' https://unpkg.com/vue@3/dist/vue.global.js;style-src 'self' 'nonce-20gXfzoeWpjyg1ryUkWAma5gMWNN03xH' https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css

Dengan menggunakan pendekatan di atas, kami dapat sangat meningkatkan keamanan halaman kami, karena kami sekarang hanya mengizinkan skrip, gaya, dan gambar yang tepat yang ingin kami muat.

Namun, seperti yang Anda bayangkan, melakukan ini untuk proyek yang lebih besar dapat menjadi membosankan dan menghabiskan waktu karena Anda harus menentukan setiap aset yang Anda muat dari sumber eksternal. Jadi ini adalah sesuatu yang perlu Anda pertimbangkan berdasarkan proyek per proyek.

Menambahkan Nonce ke CSP Anda

Sekarang kita telah melihat bagaimana kita dapat mengizinkan aset eksternal untuk dimuat, kita juga perlu melihat bagaimana kita dapat mengizinkan skrip inline untuk dijalankan.

Anda mungkin ingat bahwa kami memiliki dua blok skrip sebaris dalam tampilan Blade kami di atas :

  • Memuat JS yang ingin kami jalankan
  • Disuntikkan oleh skrip jahat dan menjalankan beberapa kode jahat!

Skrip ditambahkan ke bagian bawah tampilan Blade seperti ini :

<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>

Untuk mengizinkan skrip inline dijalankan, kita dapat menggunakan "nonces". Nonce adalah string acak yang dibuat untuk setiap permintaan.

String ini kemudian ditambahkan ke header CSP (ditambahkan melalui kebijakan Dasar yang kami perluas), dan setiap skrip sebaris yang dimuat harus menyertakan nonce ini dalam atribut nonce-nya.

Mari perbarui tampilan Blade kita untuk menyertakan nonce untuk skrip inline aman kita dengan menggunakan helper csp_nonce() yang disediakan oleh paket :

<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>

Sebagai hasil dari melakukan ini, skrip sebaris aman kami sekarang akan dijalankan seperti yang diharapkan.

Sedangkan skrip yang disuntikkan yang tidak memiliki atribut nonce akan diblokir agar tidak berjalan.

Menggunakan Tag Meta

Kecil kemungkinannya, tetapi Anda mungkin menemukan bahwa konten header Kebijakan-Keamanan-Konten Anda melebihi panjang maksimum yang diizinkan. Jika demikian, kita dapat menambahkan tag meta ke halaman kita yang menampilkan aturan untuk browser kita.

Untuk melakukan ini, Anda dapat menambahkan direktif Blade @cspMetaTag paket ke tag <head> tampilan Anda seperti ini :

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

Menggunakan contoh CustomPolicy kami di atas, ini akan menampilkan tag meta berikut :

<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/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">

Tips Menerapkan CSP di Aplikasi Laravel yang Sudah Ada

Menambahkan CSP ke aplikasi yang sudah ada terkadang bisa menjadi tugas yang cukup sulit. Sangat mudah merusak antarmuka pengguna Anda dengan menerapkan CSP yang terlalu ketat, atau lupa menambahkan aturan untuk aset tertentu yang mungkin hanya digunakan pada satu halaman.

Saya akan mengangkat tangan dan mengakui bahwa saya pernah melakukan ini sendiri sebelumnya.

Jadi, jika Anda mendapat kesempatan untuk mengimplementasikan CSP saat pertama kali memulai aplikasi baru, saya sangat menyarankan untuk melakukannya.

Jauh lebih mudah untuk menulis kebijakan bersamaan dengan membangun aplikasi.

Ada kemungkinan kecil Anda lupa menambahkan aturan khusus dan Anda bahkan dapat menambahkan aturan kebijakan di git commit yang sama seperti Anda telah menambahkan aset sehingga Anda dapat dengan mudah melacaknya di masa mendatang.

Namun, jika Anda menambahkan CSP ke aplikasi yang sudah ada, ada beberapa hal yang dapat dilakukan untuk mempermudah proses bagi Anda dan pengguna.

Pertama, Anda dapat mengaktifkan mode "hanya laporan" untuk kebijakan Anda. Ini memungkinkan Anda untuk menentukan kebijakan Anda, tetapi setiap kali ada aturan yang dilanggar (seperti memuat aset yang tidak diizinkan untuk dimuat), laporan akan dikirim ke URL yang diberikan, bukan memblokir pemuatan aset.

Dengan melakukan ini, Anda dapat membuat CSP yang ingin digunakan dan mengujinya di lingkungan produksi tanpa merusak aplikasi untuk pengguna. Anda kemudian dapat menggunakan laporan untuk mengidentifikasi aset apa pun yang Anda lewatkan dan menambahkannya ke polis Anda.

Untuk mengaktifkan pelaporan untuk kebijakan Anda, pertama-tama Anda harus menyetel URL tujuan permintaan dibuat saat pelanggaran terdeteksi. Anda dapat menambahkan ini dengan menyetel bidang CSP_REPORT_URI di file .env Anda seperti ini :

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

Anda kemudian dapat menggunakan metode reportOnly dalam kebijakan Anda. Jika kami memperbarui kebijakan untuk hanya melaporkan pelanggaran, akan terlihat seperti ini :

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/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css'])
            ->addDirective(Directive::IMG, 'https://laravel.com/img/logotype.min.svg')
            ->reportOnly();
    }
}

Sebagai hasil dari penggunaan metode reportOnly, tajuk Content-Security-Policy-Report-Only akan ditambahkan ke respons alih-alih tajuk Content-Security-Policy. Kebijakan di atas akan menghasilkan tajuk yang terlihat seperti ini :

report-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/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css

Setelah jangka waktu tertentu (mungkin beberapa hari, minggu, atau bulan), jika Anda belum menerima laporan apa pun, dan Anda yakin bahwa kebijakan tersebut sesuai, Anda dapat mengaktifkannya.

Artinya, Anda masih bisa mendapatkan laporan jika ada pelanggaran, tetapi Anda juga bisa mendapatkan manfaat keamanan penuh dari penerapan kebijakan, karena setiap pelanggaran akan diblokir. Untuk melakukannya, Anda dapat menghapus pemanggilan metode reportOnly dari kelas kebijakan Anda.

Selain itu, Anda mungkin juga merasa perlu untuk meningkatkan ketatnya aturan Anda secara bertahap seperti yang telah kami bahas sebelumnya di artikel ini.

Jadi mungkin ingin hanya menggunakan domain atau wildcard di CSP awal Anda dan kemudian secara bertahap mengubah aturan untuk menggunakan URL yang lebih spesifik.

Secara keseluruhan, menurut saya kunci untuk mengadopsi penggunaan CSP dalam aplikasi Anda yang sudah ada adalah dengan melakukan pendekatan secara bertahap. Sangat mungkin untuk menambahkan semuanya sekaligus, tetapi Anda dapat mengurangi kemungkinan kesalahan dan bug dengan mengambil pendekatan yang lebih inkremental.

Kesimpulan

Mudah-mudahan, artikel ini memberi Anda gambaran umum tentang CSP, masalah yang mereka selesaikan, dan cara kerjanya. Anda juga seharusnya sudah mengetahui cara mengimplementasikan CSP di aplikasi Laravel Anda sendiri menggunakan paket spatie/laravel-csp.

Anda mungkin juga ingin melihat dokumentasi MDN di CSP, yang menjelaskan lebih banyak opsi yang tersedia untuk Anda gunakan dalam aplikasi Anda.

OOKINFO

Jasa pembuatan Aplikasi Mobile dan Web. Siap memberikan solusi digital untuk bisnis anda. Tugas Kuliah, Organisasi, Perusahaan, E-Commerce.