Browser Support
Pembuatan skrip lintas situs (XSS), yaitu kemampuan untuk menyisipkan skrip berbahaya ke dalam 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 memitigasi XSS, bukan CSP berbasis daftar yang diizinkan host yang umum digunakan yang sering kali membuat halaman rentan terhadap XSS karena dapat dilewati di sebagian besar konfigurasi.
Istilah penting: Nonce adalah angka acak yang hanya digunakan satu kali yang dapat Anda gunakan untuk menandai tag
<script> sebagai tepercaya.
Istilah utama: 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 inline
<script> sebagai tepercaya.
Kebijakan Keamanan Konten berdasarkan nonce atau hash sering disebut sebagai CSP ketat. Jika aplikasi menggunakan CSP ketat, penyerang yang menemukan kelemahan injeksi HTML umumnya tidak dapat menggunakannya untuk memaksa browser mengeksekusi skrip berbahaya dalam dokumen yang rentan. Hal ini karena CSP ketat hanya mengizinkan skrip yang di-hash atau skrip dengan nilai nonce yang benar yang dihasilkan di server, sehingga penyerang tidak dapat menjalankan skrip tanpa mengetahui nonce yang benar untuk respons tertentu.
Mengapa Anda harus menggunakan CSP ketat?
Jika situs Anda sudah memiliki CSP yang terlihat seperti script-src www.googleapis.com,
CSP tersebut mungkin tidak efektif terhadap lintas situs. Jenis CSP ini disebut
CSP daftar yang diizinkan. Fitur ini memerlukan banyak penyesuaian dan dapat
dilewati oleh penyerang.
CSP ketat berdasarkan nonce atau hash kriptografi menghindari kendala ini.
Struktur CSP ketat
Kebijakan Keamanan Konten ketat dasar menggunakan salah satu header respons HTTP berikut:
CSP ketat berbasis nonce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none39;;
base-uri 'none';
CSP ketat berbasis hash
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none39;;
base-uri 'none';
Properti berikut membuat CSP seperti ini menjadi "ketat" dan oleh karena itu aman:
- CSP 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. - Setelan ini menetapkan
'strict-dynamic'untuk mengurangi upaya men-deploy CSP berbasis nonce atau hash dengan secara otomatis mengizinkan eksekusi skrip yang dibuat oleh skrip tepercaya. Hal ini juga membuka penggunaan sebagian besar library dan widget JavaScript pihak ketiga. - Fitur ini tidak didasarkan pada daftar URL yang diizinkan, sehingga tidak rentan terhadap bypass CSP umum.
- CSP memblokir skrip inline yang tidak tepercaya seperti pengendali peristiwa inline atau URI
javascript:. - Setelan ini membatasi
object-srcuntuk menonaktifkan plugin berbahaya seperti Flash. - Hal ini membatasi
base-uriuntuk memblokir penyisipan tag<base>. Hal ini mencegah penyerang mengubah lokasi skrip yang dimuat dari URL relatif.
Menerapkan CSP ketat
Untuk menerapkan CSP ketat, Anda harus:
- 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.
- Memfaktorkan ulang template HTML dan kode sisi klien untuk menghapus pola yang tidak kompatibel dengan CSP.
- Deploy CSP Anda.
Anda dapat menggunakan audit Lighthouse
(v7.3.0 dan yang lebih baru dengan tanda --preset=experimental) Praktik Terbaik
selama proses ini untuk memeriksa apakah situs Anda memiliki CSP, dan apakah CSP tersebut
cukup ketat untuk efektif melawan XSS.
Langkah 1: Putuskan 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, 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. Hal ini hanya berfungsi jika angka tidak dapat ditebak, dan baru dibuat saat runtime untuk setiap respons.
Gunakan 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 dijalankan.
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 dibuat dengan framework seperti Angular, React, atau lainnya, yang disajikan secara statis tanpa rendering sisi server.
Langkah 2: Tetapkan CSP yang ketat dan siapkan skrip Anda
Saat menetapkan CSP, Anda memiliki beberapa opsi:
- Mode hanya laporan (
Content-Security-Policy-Report-Only) atau mode penerapan (Content-Security-Policy). Dalam mode hanya laporan, CSP belum akan memblokir resource, sehingga tidak ada yang rusak di situs Anda, tetapi Anda dapat melihat error dan mendapatkan laporan untuk apa pun yang akan diblokir. Secara lokal, saat Anda menetapkan CSP, hal ini tidak terlalu penting, karena kedua mode menampilkan error di konsol browser. Jika ada, mode penerapan dapat membantu Anda menemukan resource yang diblokir oleh CSP draf Anda, karena memblokir resource dapat membuat halaman Anda terlihat rusak. Mode hanya laporan menjadi paling berguna di kemudian hari dalam proses (lihat Langkah 5). - Tag header atau HTML
<meta>. Untuk pengembangan lokal, tag<meta>dapat lebih nyaman untuk menyesuaikan CSP dan dengan cepat melihat pengaruhnya terhadap situs Anda. Namun:- Selanjutnya, saat men-deploy CSP dalam produksi, sebaiknya tetapkan CSP sebagai header HTTP.
- Jika ingin menyetel CSP dalam mode hanya laporan, Anda harus menyetelnya 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. A Nonce CSP harus:
- Nilai acak yang kuat secara kriptografis (idealnya panjangnya 128+ bit)
- Dibuat baru untuk setiap respons
- Dienkode Base64
Berikut beberapa contoh cara menambahkan nonce CSP di framework sisi server:
- Django (python)
- Express (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(&>quot;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 di 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
Anda dapat memuat skrip pihak ketiga secara dinamis menggunakan skrip inline.
<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.hea<d.appen>dChild(s);
});
/script{HASHED_INLINE_SCRIPT}. Untuk mengurangi jumlah hash, Anda dapat menggabungkan semua skrip inline
menjadi satu skrip. Untuk melihat cara kerjanya, lihat
contoh
dan kodenya.
<script src="https://example.org/fo><o.js&qu>o<t;/script script src="https://exam><ple.org>/bar.js"/script
integrity yang cocok dengan sumber yang diizinkan.
Pertimbangan pemuatan skrip
Contoh skrip inline menambahkan s.async = false untuk memastikan
bahwa 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 berhenti saat skrip dieksekusi, seperti yang terjadi pada skrip async. Namun, dengan cuplikan ini,
perlu diingat:
-
Satu atau kedua skrip dapat dieksekusi sebelum dokumen selesai
didownload. Jika Anda ingin dokumen siap saat
skrip dijalankan, tunggu peristiwa
DOMContentLoadedsebelum Anda menambahkan skrip. Jika hal ini menyebabkan masalah performa karena skrip tidak mulai didownload cukup awal, gunakan tag pra-muat lebih awal di halaman. -
defer = truetidak melakukan apa pun. Jika Anda memerlukan perilaku tersebut, jalankan skrip secara manual saat diperlukan.
Langkah 3: Refaktorkan template HTML dan kode sisi klien
Handler peristiwa inline (seperti onclick="…", onerror="…") dan URI JavaScript
(<a href="javascript:…">) dapat digunakan untuk menjalankan skrip. Artinya, penyerang yang menemukan bug XSS dapat menyuntikkan HTML semacam ini dan menjalankan JavaScript berbahaya. CSP berbasis nonce atau hash melarang penggunaan markup semacam ini.
Jika situs Anda menggunakan salah satu pola ini, Anda harus memfaktorkan ulang pola tersebut menjadi alternatif yang lebih aman.
Jika Anda mengaktifkan CSP pada langkah sebelumnya, Anda akan dapat melihat pelanggaran CSP di konsol setiap kali CSP memblokir pola yang tidak kompatibel.
Biasanya, perbaikannya cukup mudah:
Memfaktorkan ulang pengendali peristiwa inline
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
document.getElementById('things').addEventL<istener>('click', doThings);
/script<span onclick="doThing>s();&quo<t;A t>hing./span
URI Refactor javascript:
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
document.getElementById('foo').addEventList<ener(>39;click', linkClicked);
/script<a href="javascript:linkClick>ed(<)&>quot;foo/a
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 tetap 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 contoh ini dan contoh lainnya dari refactoring tersebut di codelab CSP ketat ini:
Langkah 4 (Opsional): Tambahkan penggantian untuk mendukung browser versi lama
Browser Support
Jika Anda perlu mendukung versi browser lama:
- Penggunaan
strict-dynamicmemerlukan penambahanhttps:sebagai penggantian untuk Safari versi sebelumnya. Jika Anda melakukannya:- Semua browser yang mendukung
strict-dynamicakan mengabaikan penggantianhttps:, sehingga hal ini tidak akan mengurangi kekuatan kebijakan. - Di browser lama, skrip yang bersumber dari luar hanya dapat dimuat jika berasal dari
origin HTTPS. Cara ini kurang aman dibandingkan CSP yang ketat, tetapi masih mencegah beberapa penyebab umum XSS seperti injeksi URI
javascript:.
- Semua browser yang mendukung
- Untuk memastikan kompatibilitas dengan versi browser yang sangat lama (4+ tahun), Anda dapat menambahkan
unsafe-inlinesebagai penggantian. Semua browser terbaru mengabaikanunsafe-inlinejika nonce atau hash CSP ada.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 9;none';
base-uri 'none';
Langkah 5: Deploy CSP Anda
Setelah mengonfirmasi bahwa CSP Anda tidak memblokir skrip yang sah di lingkungan pengembangan lokal, Anda dapat men-deploy CSP ke staging, lalu ke lingkungan produksi:
- (Opsional) Deploy CSP dalam mode hanya laporan menggunakan header
Content-Security-Policy-Report-Only. Mode hanya laporan berguna untuk menguji potensi perubahan yang merusak seperti CSP baru dalam produksi sebelum Anda mulai menerapkan batasan 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 akan rusak bagi pengguna akhir Anda. Untuk mengetahui informasi selengkapnya, lihat Reporting API. - Jika Anda yakin bahwa CSP tidak akan merusak situs Anda bagi pengguna akhir, deploy CSP menggunakan header respons
Content-Security-Policy. Sebaiknya tetapkan CSP menggunakan header HTTP sisi server karena lebih aman daripada tag<meta>. Setelah Anda menyelesaikan langkah ini, CSP Anda akan mulai melindungi aplikasi Anda dari XSS.
Batasan
CSP ketat umumnya memberikan lapisan keamanan tambahan yang kuat yang membantu memitigasi 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 ketika CSP tidak melindungi aplikasi Anda dengan baik:
- Jika Anda memberi nonce pada skrip, tetapi ada injeksi langsung ke isi atau parameter
srcdari 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 DOMscriptberdasarkan nilai argumennya. Hal ini mencakup beberapa API umum seperti.html()jQuery, serta.get()dan.post()di jQuery < 3.0. - Jika ada injeksi template di aplikasi AngularJS lama. Penyerang yang dapat menyuntikkan ke template AngularJS dapat menggunakannya untuk mengeksekusi JavaScript arbitrer.
- Jika kebijakan berisi
'unsafe-eval', injeksi keeval(),setTimeout(), dan beberapa API lain yang jarang digunakan.
Developer dan engineer keamanan harus memberikan perhatian khusus pada pola tersebut selama peninjauan kode dan audit keamanan. Anda dapat menemukan detail selengkapnya tentang kasus ini di Content Security Policy: A Successful Mess Between Hardening and Mitigation.