Kebijakan Keamanan Konten dapat secara signifikan mengurangi risiko dan dampak serangan skrip lintas situs di browser modern.
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.
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 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 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 mengandalkaneval
. Operasi JSON yang aman tersedia di setiap browser sejak IE8. Anda harus menulis ulang panggilan
setTimeout
atausetInterval
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 adaeval
. 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 direktifchild-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@.