Pembuatan skrip lintas situs (XSS), kemampuan untuk memasukkan skrip berbahaya ke aplikasi web, telah menjadi salah satu kerentanan keamanan web terbesar selama lebih dari satu dekade.
Kebijakan Keamanan Konten (CSP)
adalah lapisan keamanan tambahan yang membantu mengurangi XSS. Untuk mengonfigurasi CSP, tambahkan header HTTP Content-Security-Policy
ke halaman web dan tetapkan nilai yang mengontrol resource yang dapat dimuat agen pengguna untuk halaman tersebut.
Halaman ini menjelaskan cara menggunakan CSP berdasarkan nonce atau hash untuk mengurangi XSS, bukan CSP berbasis daftar yang diizinkan host yang biasa digunakan dan sering membiarkan halaman ekspos ke XSS karena dapat diabaikan di sebagian besar konfigurasi.
Istilah kunci: Nonce adalah angka acak yang hanya digunakan sekali dan dapat Anda gunakan untuk menandai
tag <script>
sebagai tepercaya.
Istilah kunci: Fungsi hash adalah fungsi matematika yang mengonversi nilai input menjadi nilai numerik terkompresi yang disebut hash. Anda dapat menggunakan hash
(misalnya, SHA-256) untuk menandai tag
<script>
inline sebagai tepercaya.
Kebijakan Keamanan Konten yang didasarkan pada nonce atau hash sering disebut CSP ketat. Saat aplikasi menggunakan CSP ketat, penyerang yang menemukan kekurangan injeksi HTML umumnya tidak dapat menggunakannya untuk memaksa browser mengeksekusi skrip berbahaya dalam dokumen yang rentan. Hal ini karena CSP ketat hanya mengizinkan skrip atau skrip yang di-hash dengan nilai nonce yang benar dan dihasilkan di server, sehingga penyerang tidak dapat mengeksekusi skrip tanpa mengetahui nonce yang benar untuk respons yang diberikan.
Mengapa Anda harus menggunakan CSP ketat?
Jika situs Anda sudah memiliki CSP yang terlihat seperti script-src www.googleapis.com
,
CSP mungkin tidak efektif terhadap lintas situs. Jenis CSP ini disebut CSP daftar yang diizinkan. Serangan ini memerlukan banyak penyesuaian dan dapat
diabaikan oleh penyerang.
CSP ketat berdasarkan nonce atau hash kriptografis menghindari kesalahan ini.
Struktur CSP ketat
Kebijakan Keamanan Konten dasar yang ketat menggunakan salah satu header respons HTTP berikut:
CSP ketat berbasis nonce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
![](https://web.dev/static/articles/strict-csp/image/er4BaGCJzBwDaESFKfZd.jpg?authuser=19&hl=id)
CSP ketat berbasis hash
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Properti berikut membuat CSP seperti ini "ketat" sehingga aman:
- Metode ini menggunakan nonce
'nonce-{RANDOM}'
atau hash'sha256-{HASHED_INLINE_SCRIPT}'
untuk menunjukkan tag<script>
mana yang dipercaya oleh developer situs untuk dieksekusi di browser pengguna. - Skrip ini menetapkan
'strict-dynamic'
untuk mengurangi upaya deployment CSP berbasis nonce atau hash dengan otomatis mengizinkan eksekusi skrip yang dibuat oleh skrip tepercaya. Tindakan ini juga akan menghentikan penggunaan sebagian besar widget dan library JavaScript pihak ketiga. - Hal ini tidak didasarkan pada daftar URL yang diizinkan, sehingga tidak mengalami pengabaikan CSP umum.
- Library ini akan memblokir skrip inline yang tidak tepercaya seperti pengendali peristiwa inline atau URI
javascript:
. - Kebijakan ini membatasi
object-src
untuk menonaktifkan plugin berbahaya seperti Flash. - Ini membatasi
base-uri
untuk memblokir injeksi tag<base>
. Hal ini mencegah penyerang mengubah lokasi skrip yang dimuat dari URL relatif.
Mengadopsi CSP yang ketat
Untuk mengadopsi CSP yang ketat, Anda perlu:
- Tentukan apakah aplikasi Anda harus menetapkan CSP berbasis nonce atau hash.
- Salin CSP dari bagian Struktur CSP Ketat dan tetapkan sebagai header respons di seluruh aplikasi Anda.
- Faktorkan ulang template HTML dan kode sisi klien untuk menghapus pola yang tidak kompatibel dengan CSP.
- Deploy CSP Anda.
Anda dapat menggunakan Lighthouse (v7.3.0 dan yang lebih tinggi dengan flag --preset=experimental
) melakukan audit Praktik Terbaik selama proses ini untuk memeriksa apakah situs Anda memiliki CSP, dan apakah situs tersebut cukup ketat untuk efektif terhadap XSS.
![Peringatan laporan Lighthouse bahwa tidak ada CSP yang ditemukan dalam mode penerapan.](https://web.dev/static/articles/strict-csp/image/lighthouse-report-warning-74f66e2ef76dc.png?authuser=19&hl=id)
Langkah 1: Tentukan apakah Anda memerlukan CSP berbasis nonce atau hash
Berikut cara kerja dua jenis CSP ketat:
CSP berbasis nonce
Dengan CSP berbasis nonce, Anda membuat angka acak saat runtime, menyertakannya dalam CSP Anda, dan mengaitkannya dengan setiap tag skrip di halaman Anda. Penyerang tidak dapat menyertakan atau menjalankan skrip berbahaya di halaman Anda karena mereka harus menebak angka acak yang benar untuk skrip tersebut. Ini hanya berfungsi jika angkanya tidak dapat ditebak, dan baru dibuat saat runtime untuk setiap respons.
Menggunakan CSP berbasis nonce untuk halaman HTML yang dirender di server. Untuk halaman ini, Anda dapat membuat angka acak baru untuk setiap respons.
CSP berbasis hash
Untuk CSP berbasis hash, hash setiap tag skrip inline ditambahkan ke CSP. Setiap skrip memiliki hash yang berbeda. Penyerang tidak dapat menyertakan atau menjalankan skrip berbahaya di halaman Anda karena hash skrip tersebut harus ada di CSP Anda agar dapat berjalan.
Gunakan CSP berbasis hash untuk halaman HTML yang ditayangkan secara statis, atau halaman yang perlu di-cache. Misalnya, Anda dapat menggunakan CSP berbasis hash untuk aplikasi web satu halaman yang dibangun dengan framework seperti Angular, React, atau lainnya, yang disalurkan secara statis tanpa rendering sisi server.
Langkah 2: Tetapkan CSP yang ketat dan siapkan skrip Anda
Saat menyetel CSP, Anda memiliki beberapa opsi:
- Mode hanya laporan (
Content-Security-Policy-Report-Only
) atau mode penerapan (Content-Security-Policy
). Dalam mode khusus laporan, CSP belum akan memblokir resource, sehingga tidak ada yang rusak di situs Anda, tetapi Anda dapat melihat error dan mendapatkan laporan tentang hal apa pun yang telah diblokir. Secara lokal, saat Anda menyetel CSP, hal ini tidak terlalu penting, karena kedua mode menunjukkan error di konsol browser. Jika ada, mode penerapan dapat membantu Anda menemukan resource yang diblokir draf CSP, karena memblokir resource dapat membuat halaman Anda terlihat rusak. Mode hanya laporan akan menjadi paling berguna nanti dalam proses ini (lihat Langkah 5). - Tag
<meta>
header atau HTML. Untuk pengembangan lokal, tag<meta>
mungkin lebih mudah untuk menyesuaikan CSP dan melihat dengan cepat pengaruhnya terhadap situs Anda. Namun:- Selanjutnya, saat men-deploy CSP dalam produksi, sebaiknya setel CSP sebagai header HTTP.
- Jika ingin menetapkan CSP dalam mode laporan saja, Anda harus menetapkannya sebagai header, karena tag meta CSP tidak mendukung mode hanya laporan.
Tetapkan header respons HTTP Content-Security-Policy
berikut di aplikasi Anda:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Membuat nonce untuk CSP
Nonce adalah angka acak yang hanya digunakan satu kali per pemuatan halaman. CSP berbasis nonce hanya dapat memitigasi XSS jika penyerang tidak dapat menebak nilai nonce. Nonce CSP harus:
- Nilai acak yang kuat secara kriptografis (idealnya panjangnya 128+ bit)
- Baru dibuat untuk setiap respons
- Dienkode Base64
Berikut beberapa contoh cara menambahkan nonce CSP dalam framework sisi server:
- Django (python)
- Ekspres (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
Menambahkan atribut nonce
ke elemen <script>
Dengan CSP berbasis nonce, setiap elemen <script>
harus memiliki atribut nonce
yang cocok dengan nilai nonce acak yang ditentukan dalam header CSP. Semua skrip dapat memiliki nonce
yang sama. Langkah pertama adalah menambahkan atribut ini ke semua skrip agar CSP mengizinkannya.
Tetapkan header respons HTTP Content-Security-Policy
berikut di aplikasi Anda:
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Untuk beberapa skrip inline, sintaksisnya adalah sebagai berikut:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Memuat skrip yang bersumber secara dinamis
Karena hash CSP didukung di seluruh browser hanya untuk skrip inline, Anda harus memuat semua skrip pihak ketiga secara dinamis menggunakan skrip inline. Hash untuk skrip yang bersumber tidak didukung dengan baik di seluruh browser.
![](https://web.dev/static/articles/strict-csp/image/B2YsfJDYw8PRI6kJI7Bs.jpg?authuser=19&hl=id)
<script> var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js']; scripts.forEach(function(scriptUrl) { var s = document.createElement('script'); s.src = scriptUrl; s.async = false; // to preserve execution order document.head.appendChild(s); }); </script>Agar skrip ini berjalan, Anda harus menghitung hash skrip inline dan menambahkannya ke header respons CSP, yang menggantikan placeholder
{HASHED_INLINE_SCRIPT}
. Untuk mengurangi jumlah hash, Anda dapat menggabungkan semua skrip
inline menjadi satu skrip. Untuk melihat penerapannya, lihat
contoh
ini dan kodenya.
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>CSP memblokir skrip ini karena hanya skrip inline yang dapat di-hash.
Pertimbangan pemuatan skrip
Contoh skrip inline menambahkan s.async = false
untuk memastikan
foo
dieksekusi sebelum bar
, meskipun
bar
dimuat terlebih dahulu. Dalam cuplikan ini, s.async = false
tidak memblokir parser saat skrip dimuat, karena skrip
ditambahkan secara dinamis. Parser hanya akan berhenti saat skrip dieksekusi, seperti
yang terjadi untuk skrip async
. Namun, perhatikan cuplikan ini:
-
Salah satu atau kedua skrip dapat dieksekusi sebelum dokumen selesai didownload. Jika Anda ingin dokumen sudah siap pada saat skrip dieksekusi, tunggu peristiwa
DOMContentLoaded
sebelum Anda menambahkan skrip. Jika hal ini menyebabkan masalah performa karena skrip tidak mulai didownload cukup awal, gunakan tag pramuat di awal halaman. -
defer = true
tidak melakukan apa pun. Jika Anda memerlukan perilaku tersebut, jalankan skrip secara manual saat diperlukan.
Langkah 3: Faktorkan ulang template HTML dan kode sisi klien
Pengendali peristiwa inline (seperti onclick="…"
, onerror="…"
) dan URI JavaScript (<a href="javascript:…">
) dapat digunakan untuk menjalankan skrip. Artinya,
penyerang yang menemukan bug XSS dapat memasukkan HTML semacam ini dan mengeksekusi JavaScript
berbahaya. CSP berbasis nonce atau hash melarang penggunaan markup jenis ini.
Jika situs Anda menggunakan salah satu pola ini, Anda harus memfaktorkan ulang pola tersebut menjadi alternatif yang lebih aman.
Jika mengaktifkan CSP di langkah sebelumnya, Anda akan dapat melihat pelanggaran CSP di konsol setiap kali CSP memblokir pola yang tidak kompatibel.
![Laporan pelanggaran CSP di konsol developer Chrome.](https://web.dev/static/articles/strict-csp/image/csp-violation-reports-th-ff599b24b17be.jpg?authuser=19&hl=id)
Pada umumnya, perbaikannya mudah:
Memfaktorkan ulang pengendali peristiwa inline
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>CSP memungkinkan pengendali peristiwa yang didaftarkan menggunakan JavaScript.
<span onclick="doThings();">A thing.</span>CSP memblokir pengendali peristiwa inline.
Memfaktorkan ulang URI javascript:
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>CSP memungkinkan pengendali peristiwa yang didaftarkan menggunakan JavaScript.
<a href="javascript:linkClicked()">foo</a>CSP memblokir JavaScript: URI.
Hapus eval()
dari JavaScript Anda
Jika aplikasi Anda menggunakan eval()
untuk mengonversi serialisasi string JSON menjadi objek JS, Anda harus memfaktorkan ulang instance tersebut ke JSON.parse()
, yang juga
lebih cepat.
Jika tidak dapat menghapus semua penggunaan eval()
, Anda masih dapat menetapkan CSP berbasis nonce yang ketat, tetapi Anda harus menggunakan kata kunci CSP 'unsafe-eval'
, yang membuat kebijakan Anda sedikit kurang aman.
Anda dapat menemukan hal ini dan contoh pemfaktoran ulang lainnya di codelab CSP yang ketat ini:
Langkah 4 (Opsional): Tambahkan penggantian untuk mendukung versi browser lama
Jika Anda perlu mendukung browser versi lama:
- Penggunaan
strict-dynamic
memerlukan penambahanhttps:
sebagai penggantian untuk versi Safari sebelumnya. Jika Anda melakukannya:- Semua browser yang mendukung
strict-dynamic
akan mengabaikan penggantianhttps:
, sehingga tidak akan mengurangi kekuatan kebijakan. - Di browser lama, skrip yang bersumber secara eksternal hanya dapat dimuat jika berasal dari
asal HTTPS. Ini kurang aman dibandingkan CSP yang ketat, tetapi masih mencegah beberapa penyebab XSS umum seperti injeksi URI
javascript:
.
- Semua browser yang mendukung
- Untuk memastikan kompatibilitas dengan versi browser yang sangat lama (4 tahun ke atas), Anda dapat menambahkan
unsafe-inline
sebagai penggantian. Semua browser terbaru mengabaikanunsafe-inline
jika ada nonce atau hash CSP.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Langkah 5: Deploy CSP Anda
Setelah mengonfirmasi bahwa CSP Anda tidak memblokir skrip apa pun yang sah di lingkungan pengembangan lokal, Anda dapat men-deploy CSP ke staging, lalu ke lingkungan produksi:
- (Opsional) Deploy CSP Anda dalam mode hanya laporan menggunakan header
Content-Security-Policy-Report-Only
. Mode hanya laporan berguna untuk menguji perubahan yang berpotensi menyebabkan gangguan seperti CSP baru dalam produksi sebelum Anda mulai menerapkan pembatasan CSP. Dalam mode hanya laporan, CSP Anda tidak memengaruhi perilaku aplikasi, tetapi browser tetap menghasilkan error konsol dan laporan pelanggaran saat menemukan pola yang tidak kompatibel dengan CSP Anda, sehingga Anda dapat melihat apa yang mungkin rusak bagi pengguna akhir Anda. Untuk informasi selengkapnya, lihat Reporting API. - Setelah yakin bahwa CSP Anda tidak akan merusak situs untuk pengguna akhir, deploy CSP Anda menggunakan header respons
Content-Security-Policy
. Sebaiknya tetapkan CSP Anda menggunakan sisi server header HTTP karena lebih aman daripada tag<meta>
. Setelah menyelesaikan langkah ini, CSP Anda akan mulai melindungi aplikasi Anda dari XSS.
Batasan
CSP yang ketat umumnya memberikan lapisan keamanan tambahan yang kuat untuk membantu
mengurangi XSS. Dalam sebagian besar kasus, CSP mengurangi permukaan serangan secara signifikan, dengan menolak pola berbahaya seperti URI javascript:
. Namun, berdasarkan jenis CSP yang Anda gunakan (nonce, hash, dengan atau tanpa 'strict-dynamic'
), ada kasus saat CSP juga tidak melindungi aplikasi Anda:
- Jika Anda tidak membuat skrip, tetapi ada injeksi langsung ke isi atau parameter
src
dari elemen<script>
tersebut. - Jika ada injeksi ke lokasi skrip yang dibuat secara dinamis
(
document.createElement('script')
), termasuk ke fungsi library apa pun yang membuat node DOMscript
berdasarkan nilai argumennya. Ini mencakup beberapa API umum seperti.html()
jQuery, serta.get()
dan.post()
di jQuery < 3.0. - Jika ada injeksi template dalam aplikasi AngularJS lama. Penyerang yang dapat memasukkan ke dalam template AngularJS dapat menggunakannya untuk mengeksekusi JavaScript arbitrer.
- Jika kebijakan ini berisi
'unsafe-eval'
, injeksi keeval()
,setTimeout()
, dan beberapa API lainnya yang jarang digunakan.
Developer dan engineer keamanan harus memberi perhatian khusus terhadap pola tersebut selama peninjauan kode dan audit keamanan. Anda dapat menemukan detail selengkapnya tentang kasus ini di Kebijakan Keamanan Konten: Kekacauan yang Sukses Antara Pengerasan dan Mitigasi.
Bacaan lebih lanjut
- CSP Mati dan Hidup Lama! Tentang Ketidakamanan dalam Daftar yang Diizinkan dan Masa Depan Kebijakan Keamanan Konten
- Evaluator CSP
- Konferensi LocoMoco: Kebijakan Keamanan Konten - Kekacauan yang berhasil antara hardening dan mitigasi
- Perbincangan Google I/O: Mengamankan Aplikasi Web dengan Fitur Platform Modern