Kebijakan keamanan konten

Kebijakan Keamanan Konten dapat secara signifikan mengurangi risiko dan dampak serangan skrip lintas situs di browser modern.

Joe Medley
Joe Medley

Dukungan Browser

  • Chrome: 25.
  • Edge: 14.
  • Firefox: 23.
  • Safari: 7.

Sumber

Model keamanan web didasarkan pada kebijakan asal yang sama. Misalnya, kode dari https://mybank.com hanya boleh memiliki akses ke data https://mybank.com, dan https://evil.example.com tidak boleh diizinkan aksesnya. Secara teori, setiap origin dibiarkan tetap terisolasi dari web lainnya, sehingga memberi developer sandbox yang aman untuk di-build. Namun, dalam praktiknya, penyerang telah menemukan beberapa cara untuk mengacaukan sistem.

Serangan Pembuatan skrip lintas situs (XSS), misalnya, mengabaikan kebijakan asal yang sama dengan mengelabui situs agar mengirimkan kode berbahaya bersama konten yang diinginkan. Hal ini merupakan masalah yang sangat besar, karena browser mempercayai semua kode yang muncul di halaman sebagai bagian sah dari asal keamanan halaman tersebut. Cheat Sheet XSS adalah bagian yang sudah lama, tetapi representatif dari metode yang dapat digunakan penyerang untuk melanggar kepercayaan ini dengan memasukkan kode berbahaya. Jika penyerang berhasil menyuntikkan kode apa pun, mereka telah membahayakan sesi pengguna dan mendapatkan akses ke informasi pribadi.

Halaman ini menguraikan Kebijakan Keamanan Konten (CSP) sebagai strategi untuk mengurangi risiko dan dampak serangan XSS di browser modern.

Komponen CSP

Untuk menerapkan CSP yang efektif, lakukan langkah-langkah berikut:

  • Gunakan daftar yang diizinkan untuk memberi tahu klien apa yang boleh dan apa yang tidak.
  • Pelajari direktif apa saja yang tersedia.
  • Pelajari kata kunci yang digunakannya.
  • Membatasi penggunaan kode inline dan eval().
  • Laporkan pelanggaran kebijakan ke server Anda sebelum menerapkannya.

Daftar yang diizinkan sumber

Serangan XSS mengeksploitasi ketidakmampuan browser untuk membedakan antara skrip yang merupakan bagian dari aplikasi Anda dan skrip yang telah dimasukkan dengan niat jahat oleh pihak ketiga. Misalnya, tombol Google +1 di bagian bawah halaman ini memuat dan mengeksekusi kode dari https://apis.google.com/js/plusone.js dalam konteks asal halaman ini. Kita memercayai kode tersebut, tetapi kita tidak dapat mengharapkan browser untuk mengetahuinya sendiri bahwa kode dari apis.google.com aman untuk dijalankan, sedangkan kode dari apis.evil.example.com mungkin tidak. Browser dengan senang hati mengunduh dan mengeksekusi kode yang diminta halaman, apa pun sumbernya.

Header HTTP Content-Security-Policy CSP memungkinkan Anda membuat daftar sumber konten tepercaya yang diizinkan, dan memberi tahu browser untuk hanya mengeksekusi atau merender resource dari sumber tersebut. Meskipun penyerang dapat menemukan celah untuk memasukkan skrip, skrip tersebut tidak akan cocok dengan daftar yang diizinkan, sehingga tidak akan dieksekusi.

Kami memercayai apis.google.com untuk mengirimkan kode yang valid, dan kami memercayai diri sendiri untuk melakukan hal yang sama. Berikut adalah contoh kebijakan yang memungkinkan skrip dieksekusi hanya jika berasal dari salah satu dari dua sumber tersebut:

Content-Security-Policy: script-src 'self' https://apis.google.com

script-src adalah perintah yang mengontrol serangkaian hak istimewa terkait skrip untuk halaman. Header ini 'self' sebagai salah satu sumber skrip yang valid, dan https://apis.google.com sebagai sumber lainnya. Browser kini dapat mendownload dan mengeksekusi JavaScript dari apis.google.com melalui HTTPS, serta dari origin halaman saat ini, tetapi tidak dari origin lain. Jika penyerang memasukkan kode ke dalam situs Anda, browser akan menampilkan error dan tidak menjalankan skrip yang dimasukkan.

Error konsol: Menolak memuat skrip 'http://evil.example.com/evil.js' karena melanggar perintah Kebijakan Keamanan Konten berikut: script-src 'self' https://apis.google.com
Konsol menampilkan error saat skrip mencoba dijalankan dari origin yang tidak ada dalam daftar yang diizinkan.

Kebijakan berlaku pada berbagai macam resource

CSP menyediakan serangkaian direktif kebijakan yang memungkinkan kontrol terperinci atas resource yang diizinkan untuk dimuat halaman, termasuk script-src dari contoh sebelumnya.

Daftar berikut menguraikan direktif resource lainnya mulai level 2. Spesifikasi level 3 telah dibuat drafnya, tetapi sebagian besar tidak diterapkan di browser utama.

base-uri
Membatasi URL yang dapat muncul di elemen <base> halaman.
child-src
Mencantumkan URL untuk pekerja dan konten bingkai yang disematkan. Misalnya, child-src https://youtube.com memungkinkan penyematan video dari YouTube, tetapi tidak dari sumber lain.
connect-src
Membatasi origin yang dapat Anda hubungkan menggunakan XHR, WebSocket, dan EventSource.
font-src
Menentukan origin yang dapat menayangkan font web. Misalnya, Anda dapat mengizinkan font web Google menggunakan font-src https://themes.googleusercontent.com.
form-action
Mencantumkan endpoint yang valid untuk pengiriman dari tag <form>.
frame-ancestors
Menentukan sumber yang dapat menyematkan halaman saat ini. Perintah ini berlaku untuk tag <frame>, <iframe>, <embed>, dan <applet>. Tag ini tidak dapat digunakan dalam tag <meta> atau untuk resource HTML.
frame-src
Perintah ini tidak digunakan lagi di level 2, tetapi dipulihkan di level 3. Jika tidak ada, browser akan kembali ke child-src.
img-src
Menentukan asal gambar yang dapat dimuat.
media-src
Membatasi origin yang diizinkan untuk mengirimkan video dan audio.
object-src
Memungkinkan kontrol atas Flash dan plugin lainnya.
plugin-types
Membatasi jenis plugin yang dapat dipanggil halaman.
report-uri
Menentukan URL tujuan pengiriman laporan browser saat kebijakan keamanan konten dilanggar. Direktif ini tidak dapat digunakan di tag <meta>.
style-src
Membatasi asal halaman yang dapat menggunakan stylesheet.
upgrade-insecure-requests
Memberi petunjuk kepada agen pengguna untuk menulis ulang skema URL dengan mengubah HTTP menjadi HTTPS. Direktif ini ditujukan untuk situs dengan banyak URL lama yang perlu ditulis ulang.
worker-src
Perintah CSP Level 3 yang membatasi URL yang dapat dimuat sebagai pekerja, pekerja bersama, atau pekerja layanan. Mulai Juli 2017, perintah ini memiliki penerapan terbatas.

Secara default, browser memuat resource terkait dari asal mana pun, tanpa batasan, kecuali jika Anda menetapkan kebijakan dengan perintah tertentu. Untuk mengganti default, tentukan perintah default-src. Perintah ini menentukan default untuk direktif yang tidak ditentukan yang diakhiri dengan -src. Misalnya, jika Anda menetapkan default-src ke https://example.com, dan tidak menentukan perintah font-src, Anda hanya dapat memuat font dari https://example.com.

Direktif berikut tidak menggunakan default-src sebagai pengganti. Ingat bahwa tidak menyetelnya sama saja dengan mengizinkan apa pun:

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

Sintaksis CSP dasar

Untuk menggunakan perintah CSP, cantumkan perintah tersebut di header HTTP dengan perintah yang dipisahkan titik dua. Pastikan untuk mencantumkan semua resource yang diperlukan dari jenis tertentu dalam satu perintah sebagai berikut:

script-src https://host1.com https://host2.com

Berikut adalah contoh beberapa perintah, dalam hal ini untuk aplikasi web yang memuat semua resource-nya dari jaringan pengiriman konten di https://cdn.example.net, dan tidak menggunakan konten atau plugin berbingkai:

Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'

Detail implementasi

Browser modern mendukung header Content-Security-Policy tanpa awalan. Ini adalah header yang direkomendasikan. Header X-WebKit-CSP dan X-Content-Security-Policy yang mungkin Anda lihat dalam tutorial online tidak digunakan lagi.

CSP ditentukan berdasarkan halaman demi halaman. Anda harus mengirim header HTTP dengan setiap respons yang ingin dilindungi. Hal ini memungkinkan Anda menyesuaikan kebijakan untuk halaman tertentu berdasarkan kebutuhan spesifiknya. Misalnya, jika satu kumpulan halaman di situs Anda memiliki tombol +1, sementara yang lainnya tidak, Anda dapat mengizinkan kode tombol dimuat hanya jika diperlukan.

Daftar sumber untuk setiap perintah bersifat fleksibel. Anda dapat menentukan sumber berdasarkan skema (data:, https:), atau rentang kekhususan dari hostname-saja (example.com, yang cocok dengan asal apa pun di host tersebut: skema apa pun, port apa pun) ke URI yang sepenuhnya memenuhi syarat (https://example.com:443, yang hanya cocok dengan HTTPS, hanya example.com, dan hanya port 443). Karakter pengganti diterima, tetapi hanya sebagai skema, port, atau di posisi paling kiri nama host: *://*.example.com:* akan cocok dengan semua subdomain example.com (tetapi bukan example.com itu sendiri), menggunakan skema apa pun, di port apa pun.

Daftar sumber juga menerima empat kata kunci:

  • 'none' tidak cocok dengan apa pun.
  • 'self' cocok dengan origin saat ini, tetapi tidak dengan subdomainnya.
  • 'unsafe-inline' mengizinkan JavaScript dan CSS inline. Untuk informasi selengkapnya, lihat Menghindari kode inline.
  • 'unsafe-eval' mengizinkan mekanisme teks-ke-JavaScript seperti eval. Untuk mengetahui informasi selengkapnya, lihat Menghindari eval().

Kata kunci ini memerlukan tanda kutip tunggal. Misalnya, script-src 'self' (dengan tanda kutip) mengotorisasi eksekusi JavaScript dari host saat ini; script-src self (tanpa tanda kutip) memungkinkan JavaScript dari server bernama "self" (dan bukan dari host saat ini), yang mungkin bukan yang Anda maksudkan.

Menempatkan halaman Anda di sandbox

Ada satu direktif lagi yang patut dibahas: sandbox. Ini sedikit berbeda dari yang lain yang telah kita lihat, karena direktif ini menempatkan pembatasan pada tindakan yang dapat dilakukan halaman, bukan pada resource yang dapat dimuat halaman. Jika perintah sandbox ada, halaman akan diperlakukan seolah-olah dimuat di dalam <iframe> dengan atribut sandbox. Hal ini dapat memiliki berbagai efek pada halaman: memaksa halaman ke dalam origin unik, dan mencegah pengiriman formulir, dan lain-lain. Hal ini sedikit di luar cakupan halaman ini, tetapi Anda dapat menemukan detail lengkap tentang atribut sandboxing yang valid di bagian "Sandboxing" pada spesifikasi HTML5.

Tag meta

Mekanisme pengiriman yang disukai CSP adalah header HTTP. Namun, ini dapat berguna untuk menetapkan kebijakan pada halaman secara langsung di markup. Lakukan dengan menggunakan tag <meta> dengan atribut http-equiv:

<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">

Ini tidak dapat digunakan untuk frame-ancestors, report-uri, atau sandbox.

Menghindari kode inline

Meskipun sangat efektif, daftar yang diizinkan berbasis origin yang digunakan dalam perintah CSP tidak dapat mengatasi ancaman terbesar yang ditimbulkan oleh serangan XSS: injeksi skrip inline. Jika penyerang dapat memasukkan tag skrip yang langsung berisi beberapa payload berbahaya (seperti <script>sendMyDataToEvilDotCom()</script>), browser tidak memiliki cara untuk membedakannya dari tag skrip inline yang sah. CSP mengatasi masalah ini dengan mencekal skrip inline sepenuhnya.

Larangan ini tidak hanya mencakup skrip yang disematkan langsung di tag script, tetapi juga penangan peristiwa inline dan URL javascript:. Anda harus memindahkan konten tag script ke file eksternal, dan mengganti URL javascript: dan <a ... onclick="[JAVASCRIPT]"> dengan panggilan addEventListener() yang sesuai. Misalnya, Anda dapat menulis ulang kode berikut dari:

<script>
   
function doAmazingThings() {
    alert
('YOU ARE AMAZING!');
   
}
</script>
<button onclick='doAmazingThings();'>Am I amazing?</button>

menjadi seperti ini:

<!-- amazing.html -->
<script src='amazing.js'></script>
<button id='amazing'>Am I amazing?</button>
// amazing.js
function doAmazingThings() {
    alert
('YOU ARE AMAZING!');
}
document
.addEventListener('DOMContentLoaded', function () {
    document
.getElementById('amazing')
   
.addEventListener('click', doAmazingThings);
});

Kode yang ditulis ulang tidak hanya kompatibel dengan CSP, tetapi juga selaras dengan praktik terbaik desain web. JavaScript inline menggabungkan struktur dan perilaku dengan cara yang membuat kode membingungkan. Proses penyimpanan dalam cache dan kompilasi juga lebih rumit. Memindahkan kode ke dalam resource eksternal akan meningkatkan performa halaman Anda.

Memindahkan tag dan atribut style inline ke dalam stylesheet eksternal juga sangat direkomendasikan untuk melindungi situs Anda dari serangan eksfiltrasi data berbasis CSS.

Cara mengizinkan skrip dan gaya inline untuk sementara

Anda dapat mengaktifkan skrip dan gaya inline dengan menambahkan 'unsafe-inline' sebagai sumber yang diizinkan dalam perintah script-src atau style-src. CSP Level 2 juga memungkinkan Anda menambahkan skrip inline tertentu ke daftar yang diizinkan menggunakan nonce kriptografis (angka yang digunakan satu kali) atau hash sebagai berikut.

Untuk menggunakan nonce, berikan tag skrip Anda atribut nonce. Nilainya harus sama dengan yang ada dalam daftar sumber tepercaya. Contoh:

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
   
// Some inline code I can't remove yet, but need to as soon as possible.
</script>

Tambahkan nonce ke perintah script-src Anda setelah kata kunci nonce-:

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

Nonce harus dibuat ulang untuk setiap permintaan halaman, dan harus tidak dapat ditebak.

Cara kerja hash hampir sama. Sebagai ganti menambahkan kode ke tag skrip, buat hash SHA dari skrip itu sendiri dan tambahkan ke perintah script-src. Misalnya, jika halaman Anda berisi hal berikut:

<script>alert('Hello, world.');</script>

Kebijakan Anda harus berisi hal berikut:

Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

Awalan sha*- menentukan algoritma yang menghasilkan hash. Contoh sebelumnya menggunakan sha256-, tetapi CSP juga mendukung sha384- dan sha512-. Saat membuat hash, hapus tag <script>. Kapitalisasi dan spasi kosong penting, termasuk spasi kosong di awal dan di akhir.

Solusi untuk membuat hash SHA tersedia dalam sejumlah bahasa. Dengan menggunakan Chrome 40 atau yang lebih baru, Anda dapat membuka DevTools, lalu memuat ulang halaman. Tab Konsol menampilkan pesan error dengan hash SHA-256 yang benar untuk setiap skrip inline Anda.

Menghindarineval()

Meskipun penyerang tidak dapat memasukkan skrip secara langsung, mereka mungkin dapat mengelabui aplikasi Anda untuk mengonversi teks input menjadi JavaScript yang dapat dieksekusi dan mengeksekusinya atas nama mereka. eval(), new Function(), setTimeout([string], …), dan setInterval([string], ...) adalah semua vektor yang dapat digunakan penyerang untuk mengeksekusi kode berbahaya melalui teks yang dimasukkan. Respons default CSP terhadap risiko ini adalah memblokir sepenuhnya semua vektor tersebut.

Hal ini memiliki efek berikut pada cara Anda mem-build aplikasi:

  • Anda harus mengurai JSON menggunakan JSON.parse bawaan, bukan mengandalkan eval. Operasi JSON yang aman tersedia di setiap browser sejak IE8.
  • Anda harus menulis ulang panggilan setTimeout atau setInterval yang Anda buat menggunakan fungsi inline, bukan string. Misalnya, jika halaman Anda berisi hal berikut:

    setTimeout("document.querySelector('a').style.display = 'none';", 10);

    Tulis ulang sebagai:

    setTimeout(function () {
        document
    .querySelector('a').style.display = 'none';
    }, 10);
     
    ```
  • Hindari pembuatan template inline saat runtime. Banyak library pembuatan template sering menggunakan new Function() untuk mempercepat pembuatan template saat runtime, yang memungkinkan evaluasi teks berbahaya. Beberapa framework mendukung CSP secara langsung, yang melakukan fallback ke parser yang andal jika tidak ada eval. Direktif ng-csp AngularJS adalah contoh yang baik untuk hal ini. Namun, sebaiknya gunakan bahasa template yang menawarkan prakompilasi, seperti Handlebars. Prakompilasi template dapat membuat pengalaman pengguna menjadi lebih cepat daripada implementasi runtime tercepat, serta membuat situs Anda lebih aman.

Jika eval() atau fungsi text-to-JavaScript lainnya sangat penting bagi aplikasi Anda, Anda dapat mengaktifkannya dengan menambahkan 'unsafe-eval' sebagai sumber yang diizinkan dalam perintah script-src. Sebaiknya jangan lakukan hal ini karena risiko injeksi kode yang ditimbulkannya.

Melaporkan pelanggaran kebijakan

Untuk memberi tahu server tentang bug yang mungkin memungkinkan injeksi berbahaya, Anda dapat memberi tahu browser untuk POST laporan pelanggaran berformat JSON ke lokasi yang ditentukan dalam perintah report-uri:

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

Laporan ini terlihat seperti berikut:

{
   
"csp-report": {
   
"document-uri": "http://example.org/page.html",
   
"referrer": "http://evil.example.com/",
   
"blocked-uri": "http://evil.example.com/evil.js",
   
"violated-directive": "script-src 'self' https://apis.google.com",
   
"original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
   
}
}

Laporan ini berisi informasi bermanfaat untuk menemukan penyebab pelanggaran kebijakan, termasuk halaman tempat pelanggaran terjadi (document-uri), referrer halaman tersebut, resource yang melanggar kebijakan halaman (blocked-uri), perintah spesifik yang dilanggar (violated-directive), dan kebijakan lengkap halaman (original-policy).

Hanya Laporan

Jika Anda baru memulai CSP, sebaiknya gunakan mode khusus laporan untuk mengevaluasi status aplikasi sebelum mengubah kebijakan. Untuk melakukannya, bukan mengirim header Content-Security-Policy, kirim header Content-Security-Policy-Report-Only:

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

Kebijakan yang ditetapkan dalam mode hanya-lapor tidak memblokir resource yang dibatasi, tetapi akan mengirim laporan pelanggaran ke lokasi yang Anda tentukan. Anda bahkan dapat mengirim kedua header, untuk menerapkan satu kebijakan sambil memantau kebijakan yang lain. Ini adalah cara yang bagus untuk menguji perubahan pada CSP sekaligus menerapkan kebijakan saat ini: aktifkan pelaporan untuk kebijakan baru, pantau laporan pelanggaran, dan perbaiki bug apa pun, dan jika Anda puas dengan kebijakan baru, mulai terapkan kebijakan tersebut.

Penggunaan di Dunia Nyata

Langkah pertama dalam pembuatan kebijakan untuk aplikasi Anda adalah mengevaluasi resource yang dimuatnya. Setelah memahami struktur aplikasi, buat kebijakan berdasarkan persyaratannya. Bagian berikut membahas beberapa kasus penggunaan umum dan proses pengambilan keputusan untuk mendukungnya dengan mengikuti panduan CSP.

Widget media sosial

  • Tombol Suka Facebook memiliki beberapa opsi penerapan. Sebaiknya gunakan versi <iframe> agar tetap di-sandbox dari bagian lain situs Anda. Diperlukan direktif child-src https://facebook.com agar dapat berfungsi dengan baik.
  • Tombol Tweet X mengandalkan akses ke skrip. Pindahkan skrip yang disediakan ke file JavaScript eksternal, dan gunakan directive script-src https://platform.twitter.com; child-src https://platform.twitter.com.
  • Platform lain memiliki persyaratan serupa dan dapat ditangani dengan cara serupa. Untuk menguji resource ini, sebaiknya tetapkan default-src dari 'none' dan amati konsol Anda untuk menentukan resource mana yang perlu diaktifkan.

Untuk menggunakan beberapa widget, gabungkan perintah sebagai berikut:

script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com

Kunci total

Untuk beberapa situs, Anda harus memastikan bahwa hanya resource lokal yang dapat dimuat. Contoh berikut mengembangkan CSP untuk situs perbankan, dimulai dengan kebijakan default yang memblokir semuanya (default-src 'none').

Situs memuat semua gambar, gaya, dan skrip dari CDN di https://cdn.mybank.net, dan terhubung ke https://api.mybank.com/ menggunakan XHR untuk mengambil data. Fitur ini menggunakan bingkai, tetapi hanya untuk halaman yang bersifat lokal ke situs (tidak ada asal pihak ketiga). Tidak ada Flash di situs tersebut, tidak ada font, tidak ada tambahan. Header CSP paling ketat yang dapat dikirim adalah ini:

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

Khusus SSL

Berikut adalah contoh CSP untuk administrator forum yang ingin memastikan bahwa semua resource di forumnya hanya dimuat menggunakan saluran aman, tetapi tidak berpengalaman dalam coding dan tidak memiliki resource untuk menulis ulang software forum pihak ketiga yang penuh dengan skrip dan gaya inline:

Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'

Meskipun https: ditentukan dalam default-src, direktif skrip dan gaya tidak otomatis mewarisi sumber tersebut. Setiap perintah akan menimpa default untuk jenis resource tertentu tersebut.

Pengembangan standar CSP

Content Security Policy Level 2 adalah standar yang direkomendasikan oleh W3C. Web Application Security Working Group W3C sedang mengembangkan iterasi berikutnya dari spesifikasi, Content Security Policy Level 3.

Untuk mengikuti diskusi seputar fitur mendatang ini, lihat arsip milis public-webappsec@.