Kebijakan Keamanan Konten dapat mengurangi risiko dan dampak serangan skrip lintas situs secara signifikan di browser modern.
Model keamanan web didasarkan pada kebijakan origin yang sama. Misalnya,
kode dari https://mybank.com
hanya boleh memiliki akses ke data
https://mybank.com
, dan https://evil.example.com
tidak boleh mendapatkan akses yang diizinkan.
Secara teori, setiap origin tetap terisolasi dari web lainnya sehingga memberikan
sandbox yang aman bagi developer untuk membangunnya. Namun dalam praktiknya, penyerang telah menemukan
beberapa cara untuk menumbangkan sistem.
Serangan pembuatan skrip lintas situs (XSS), misalnya, mengabaikan kebijakan origin yang sama dengan mengelabui situs agar mengirimkan kode berbahaya bersama dengan konten yang dimaksud. Ini adalah masalah besar, karena browser memercayai semua kode yang muncul di halaman sebagai bagian sah dari asal keamanan halaman tersebut. Tips Praktis XSS adalah lintas bagian yang sudah lama tetapi representatif dari metode yang dapat digunakan penyerang untuk melanggar kepercayaan ini dengan memasukkan kode berbahaya. Jika penyerang berhasil memasukkan kode apa pun, berarti mereka telah membobol 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 saja yang diizinkan dan tidak diizinkan.
- Ketahui perintah yang tersedia.
- Pelajari kata kunci yang digunakannya.
- Batasi penggunaan kode inline dan
eval()
. - Laporkan pelanggaran kebijakan ke server Anda sebelum menerapkannya.
Daftar sumber yang diizinkan
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
menjalankan kode dari https://apis.google.com/js/plusone.js
dalam konteks
asal halaman ini.
Kami memercayai kode tersebut, tetapi kami tidak dapat berharap browser mengetahui sendiri
bahwa kode dari apis.google.com
aman untuk dijalankan, sedangkan kode dari
apis.evil.example.com
mungkin tidak. Browser dengan mudah mendownload dan
menjalankan kode apa pun yang diminta halaman, terlepas dari sumbernya.
Header HTTP Content-Security-Policy
CSP memungkinkan Anda membuat daftar sumber konten tepercaya yang diizinkan, dan memberi tahu browser untuk menjalankan atau merender hanya
resource dari sumber tersebut. Meskipun penyerang dapat menemukan lubang untuk memasukkan
skrip, skrip tidak akan cocok dengan daftar yang diizinkan, sehingga tidak akan
dieksekusi.
Kami memercayai apis.google.com
untuk memberikan kode yang valid, dan kami percaya diri kami
untuk melakukan hal yang sama. Berikut adalah contoh kebijakan yang memungkinkan skrip dijalankan hanya
jika skrip 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 sebuah halaman. Header ini 'self'
sebagai satu sumber skrip yang valid, dan
https://apis.google.com
sebagai satu sumber skrip yang lain. Browser kini dapat mendownload dan mengeksekusi JavaScript dari apis.google.com
melalui HTTPS, juga dari asal halaman saat ini, tetapi bukan dari origin lain. Jika penyerang memasukkan kode ke
situs Anda, browser akan menampilkan error dan tidak menjalankan skrip yang dimasukkan.
Kebijakan berlaku untuk berbagai sumber daya
CSP menyediakan serangkaian perintah kebijakan yang memungkinkan kontrol terperinci atas
resource yang boleh dimuat halaman, termasuk script-src
dari contoh
sebelumnya.
Daftar berikut menguraikan perintah resource lainnya mulai level 2. Spesifikasi level 3 telah draf, tetapi sebagian besar tidak diimplementasikan di browser utama.
base-uri
- Membatasi URL yang dapat muncul di elemen
<base>
halaman. child-src
- Mencantumkan URL untuk pekerja dan konten frame yang disematkan. Misalnya,
child-src https://youtube.com
memungkinkan penyematan video dari YouTube, tetapi tidak dari asal lain. connect-src
- Membatasi origin yang dapat Anda hubungkan menggunakan XHR, WebSockets, dan EventSource.
font-src
- Menentukan origin yang dapat menyediakan 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>
. Parameter 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 melakukan fallback ke
child-src
. img-src
- Menentukan asal image asal 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 oleh halaman.
report-uri
- Menentukan URL yang dikirim browser saat kebijakan keamanan konten
dilanggar. Perintah ini tidak dapat digunakan di tag
<meta>
. style-src
- Membatasi origin halaman yang dapat menggunakan stylesheet.
upgrade-insecure-requests
- Menginstruksikan agen pengguna untuk menulis ulang skema URL dengan mengubah HTTP ke HTTPS. Perintah ini ditujukan untuk situs dengan sejumlah besar 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 implementasi terbatas.
Secara default, browser memuat resource terkait dari asal apa pun, tanpa
batasan, kecuali jika Anda menetapkan kebijakan dengan perintah tertentu. Untuk mengganti
perintah default, tentukan perintah default-src
. Perintah ini menentukan default untuk setiap
perintah 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
.
Perintah berikut tidak menggunakan default-src
sebagai penggantian. Perlu diingat bahwa
gagal 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 di header HTTP dengan perintah yang dipisahkan dengan 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 penayangan konten (CDN) 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 {i>header<i} HTTP dengan setiap respons yang ingin Anda lindungi. Dengan begitu, Anda dapat menyesuaikan kebijakan untuk halaman tertentu berdasarkan kebutuhan spesifiknya. Misalnya, jika satu kumpulan laman di situs Anda memiliki tombol +1, sementara yang lainnya tidak, Anda dapat mengizinkan kode tombol dimuat hanya saat diperlukan.
Daftar sumber untuk setiap perintah bersifat fleksibel. Anda dapat menentukan sumber berdasarkan
skema (data:
, https:
), atau dengan rentang kekhususan dari hanya nama host
(example.com
, yang cocok dengan origin apa pun di host tersebut: skema apa pun, port apa pun) hingga
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 pada nama host: *://*.example.com:*
akan
cocok dengan semua subdomain example.com
(tetapi bukan example.com
itu sendiri), menggunakan
skema apa pun, pada port mana pun.
Daftar sumber juga menerima empat kata kunci:
'none'
tidak cocok dengan apa pun.'self'
cocok dengan asal saat ini, tetapi tidak dengan subdomainnya.'unsafe-inline'
mengizinkan JavaScript dan CSS inline. Untuk mengetahui informasi selengkapnya, lihat Menghindari kode inline.'unsafe-eval'
mengizinkan mekanisme teks ke JavaScript sepertieval
. Untuk mengetahui informasi selengkapnya, lihat Menghindarieval()
.
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 maksud.
Lakukan sandbox halaman Anda
Ada satu perintah lagi yang perlu dibahas: sandbox
. Ini sedikit berbeda dari lainnya yang telah kita lihat, karena halaman ini menerapkan pembatasan pada tindakan yang dapat dilakukan halaman, bukan pada resource yang dapat dimuat oleh halaman. Jika
ada perintah sandbox
, halaman akan diperlakukan seolah-olah dimuat
di dalam <iframe>
dengan atribut sandbox
. Hal ini dapat memiliki berbagai
efek pada halaman: memaksa halaman ke asal yang unik, dan mencegah pengiriman
formulir, di antaranya. Bagian ini sedikit di luar cakupan halaman ini, tetapi Anda dapat menemukan detail lengkap tentang atribut sandbox yang valid di bagian "Sandboxing" pada spesifikasi HTML5.
Tag meta
Mekanisme pengiriman yang disukai CSP adalah header HTTP. Namun, sebaiknya tetapkan kebijakan di halaman secara langsung di markup. Lakukan hal tersebut 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'">
Tidak dapat digunakan untuk frame-ancestors
, report-uri
, atau sandbox
.
Menghindari kode inline
Sehebat daftar yang diizinkan berbasis origin yang digunakan dalam perintah CSP, daftar tersebut
tidak dapat mengatasi ancaman terbesar yang ditimbulkan oleh serangan XSS: injeksi skrip inline.
Jika penyerang dapat menginjeksikan tag skrip yang secara langsung berisi beberapa payload
berbahaya (seperti <script>sendMyDataToEvilDotCom()</script>
), browser tidak
dapat membedakannya dari tag skrip inline yang sah. CSP mengatasi masalah ini
dengan memblokir skrip inline sepenuhnya.
Pemblokiran ini tidak hanya mencakup skrip yang disematkan langsung dalam tag script
, tetapi juga
pengendali peristiwa inline dan URL javascript:
. Anda harus memindahkan konten tag
script
ke file eksternal, serta 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:
<!-- 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. Juga lebih rumit untuk membuat cache dan kompilasi. Memindahkan kode Anda ke resource eksternal akan membuat performa halaman Anda lebih baik.
Memindahkan tag dan atribut style
inline ke stylesheet eksternal
juga sangat direkomendasikan untuk melindungi situs Anda dari
serangan pemindahan data yang tidak sah 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 direktif 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 atribut nonce pada tag skrip. Nilainya harus sama dengan salah satu 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
dengan mengikuti kata kunci nonce-
:
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Nonce harus dibuat untuk setiap permintaan halaman, dan harus tidak dapat ditebak.
{i>Hash<i} bekerja dengan cara yang serupa. Daripada menambahkan kode ke tag skrip, buatlah hash SHA skrip itu sendiri dan tambahkan ke perintah script-src
.
Misalnya, jika halaman Anda berisi hal ini:
<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>
. Pentingnya huruf besar dan spasi kosong, termasuk spasi kosong di awal dan akhir.
Solusi untuk menghasilkan hash SHA tersedia dalam berbagai 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 tidak dapat memasukkan skrip secara langsung, penyerang mungkin dapat mengelabui
aplikasi Anda agar mengubah 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 membangun aplikasi:
- Anda harus mengurai JSON menggunakan
JSON.parse
bawaan, bukan mengandalkaneval
. Operasi JSON yang aman tersedia di setiap browser sejak IE8. Anda harus menulis ulang panggilan
setTimeout
atausetInterval
yang Anda lakukan 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 menggunakan
new Function()
sering kali untuk mempercepat pembuatan template saat runtime, sehingga memungkinkan evaluasi teks berbahaya. Beberapa framework siap pakai untuk mendukung CSP, dan beralih kembali ke parser yang andal tanpaeval
. Perintah ng-csp AngularJS adalah contoh yang bagus. 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 teks ke JavaScript lainnya sangat penting untuk
aplikasi, Anda dapat mengaktifkannya dengan menambahkan 'unsafe-eval'
sebagai sumber yang diizinkan
dalam perintah script-src
. Kami sangat tidak menyarankan hal ini karena risiko injeksi kode yang ditimbulkannya.
Melaporkan pelanggaran kebijakan
Untuk memberi tahu server 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 akan 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 yang berguna untuk menemukan penyebab pelanggaran
kebijakan, termasuk halaman saat itu 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
).
Laporan Saja
Jika Anda baru mulai menggunakan CSP, sebaiknya gunakan mode hanya laporan untuk
mengevaluasi status aplikasi sebelum mengubah kebijakan. Untuk melakukannya,
kirim header Content-Security-Policy-Report-Only
, bukan mengirim header Content-Security-Policy
:
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
Kebijakan yang ditentukan dalam mode hanya laporan tidak memblokir resource yang dibatasi, tetapi mengirimkan 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 Anda sambil menerapkan kebijakan Anda saat ini: aktifkan pelaporan untuk kebijakan baru, pantau laporan pelanggaran dan perbaiki bug, dan jika Anda sudah puas dengan kebijakan baru ini, mulailah menerapkannya.
Penggunaan di Dunia Nyata
Langkah pertama dalam membuat kebijakan untuk aplikasi Anda adalah mengevaluasi resource yang dimuatnya. Setelah memahami struktur aplikasi, buat kebijakan berdasarkan persyaratannya. Bagian berikut memandu beberapa kasus penggunaan umum dan proses keputusan untuk mendukungnya dengan mengikuti pedoman CSP.
Widget media sosial
- Tombol Suka Facebook
memiliki beberapa opsi implementasi. Sebaiknya gunakan versi
<iframe>
agar tetap di-sandbox dari bagian lain situs Anda. Alat ini memerlukan perintahchild-src https://facebook.com
agar berfungsi dengan baik. - Tombol Tweet X bergantung pada akses ke skrip.
Pindahkan skrip yang disediakan ke file JavaScript eksternal, dan gunakan
perintah
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 setel
default-src
dari'none'
dan tonton 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
Karantina wilayah
Untuk beberapa situs, sebaiknya pastikan hanya resource lokal yang dapat dimuat. Contoh berikut mengembangkan CSP untuk situs perbankan, dimulai dengan kebijakan default yang memblokir semuanya (default-src 'none'
).
Situs ini 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. Proses ini menggunakan bingkai, tetapi hanya untuk halaman yang bersifat lokal ke situs (tidak ada
asal pihak ketiga). Tidak ada Flash di situs, 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 yang aman, tetapi tidak berpengalaman dalam melakukan 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:
ditetapkan dalam default-src
, perintah skrip dan gaya tidak secara otomatis mewarisi sumber tersebut. Setiap perintah menimpa
default untuk jenis resource tertentu.
Pengembangan standar CSP
Kebijakan Keamanan Konten Level 2 adalah standar yang direkomendasikan W3C. Web Application Security Working Group W3C sedang mengembangkan iterasi berikutnya dari spesifikasi ini, Content Security Policy Level 3.
Untuk mengikuti diskusi seputar fitur mendatang ini, lihat arsip milis publik-webappsec@.