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 memitigasi XSS. Untuk mengonfigurasi CSP,
tambahkan header HTTP Content-Security-Policy
ke halaman web dan tetapkan nilai yang
mengontrol sumber daya yang dapat dimuat
oleh agen pengguna untuk laman tersebut.
Halaman ini menjelaskan cara menggunakan CSP berdasarkan nonce atau hash untuk memitigasi XSS, bukan CSP berbasis daftar yang diizinkan dan biasa digunakan yang sering keluar dari halaman diekspos ke XSS karena dapat diabaikan di sebagian besar konfigurasi.
Istilah kunci: Nonce adalah angka acak yang hanya digunakan satu kali yang dapat Anda gunakan untuk menandai
<script>
sebagai tepercaya.
Istilah kunci: Fungsi hash adalah fungsi matematis yang mengonversi input
menjadi nilai numerik terkompresi yang disebut hash. Anda dapat menggunakan {i>hash<i}
(misalnya, SHA-256) untuk menandai elemen
<script>
sebagai tepercaya.
Kebijakan Keamanan Konten yang didasarkan pada {i> nonce<i} atau {i>hash<i} sering disebut sebagai CSP ketat. Bila aplikasi menggunakan CSP yang ketat, penyerang yang menemukan HTML umumnya tidak dapat digunakan untuk memaksa browser mengeksekusi skrip berbahaya dalam dokumen yang rentan. Hal ini karena CSP ketat saja memungkinkan skrip yang di-hash atau skrip dengan nilai nonce yang benar yang dihasilkan pada server, sehingga penyerang tidak dapat mengeksekusi skrip tanpa mengetahui {i>nonce<i} yang benar respons tertentu.
Mengapa Anda harus menggunakan CSP yang ketat?
Jika situs Anda sudah memiliki CSP yang terlihat seperti script-src www.googleapis.com
,
itu mungkin tidak efektif untuk lintas situs. Jenis CSP ini disebut
CSP yang diizinkan. Mereka membutuhkan banyak
penyesuaian dan dapat
diabaikan oleh penyerang.
CSP ketat berdasarkan nonce atau hash kriptografis menghindari kesalahan ini.
Struktur CSP yang ketat
Kebijakan Keamanan Konten yang ketat menggunakan salah satu respons HTTP berikut {i>header<i}:
CSP ketat berbasis nonce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
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:
- Fungsi ini menggunakan nonce
'nonce-{RANDOM}'
atau hash'sha256-{HASHED_INLINE_SCRIPT}'
untuk menunjukkan tag<script>
mana yang dipercaya developer situs untuk dieksekusi di browser pengguna. - Properti ini menetapkan
'strict-dynamic'
untuk mengurangi upaya deployment CSP berbasis nonce atau hash secara otomatis memungkinkan eksekusi skrip yang dibuat oleh skrip terpercaya. Hal ini juga membuka blokir penggunaan sebagian besar library dan widget JavaScript pihak ketiga. - Tidak didasarkan pada daftar URL yang diizinkan, sehingga tidak terpengaruh pengabaikan CSP umum.
- Fungsi ini memblokir skrip inline yang tidak tepercaya seperti pengendali peristiwa inline atau
javascript:
URI. - Kebijakan ini membatasi
object-src
untuk menonaktifkan plugin berbahaya seperti Flash. - Fungsi ini membatasi
base-uri
untuk memblokir injeksi tag<base>
. Hal ini mencegah penyerang untuk mengubah lokasi skrip yang dimuat dari URL relatif.
Menggunakan CSP yang ketat
Untuk menggunakan CSP yang ketat, Anda harus:
- Tentukan apakah aplikasi Anda harus menetapkan CSP berbasis nonce atau hash.
- Salin CSP dari bagian Struktur CSP Ketat, lalu 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 Lighthouse
(v7.3.0 dan yang lebih baru dengan tanda --preset=experimental
) Audit Praktik Terbaik
melalui proses ini untuk memeriksa apakah situs
Anda memiliki CSP, dan apakah
cukup ketat untuk
efektif terhadap XSS.
Langkah 1: Tentukan apakah Anda memerlukan CSP berbasis nonce atau hash
Berikut adalah cara kerja dua jenis CSP ketat:
CSP berbasis nonce
Dengan CSP berbasis nonce, Anda membuat angka acak saat runtime, menyertakannya dalam CSP Anda, dan kaitkan 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 itu. Cara ini hanya berfungsi jika jumlah tidak bisa 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 di halaman Anda, karena hash dari skrip tersebut harus ada di CSP untuk menjalankannya.
Gunakan CSP berbasis hash untuk halaman HTML yang ditayangkan secara statis, atau halaman yang harus di-cache. Misalnya, Anda dapat menggunakan CSP berbasis hash untuk halaman web satu halaman aplikasi yang dibangun dengan framework seperti Angular, React, atau lainnya, yang disajikan secara statis tanpa {i> rendering<i} sisi server.
Langkah 2: Tetapkan CSP yang ketat dan siapkan skrip
Saat menyetel CSP, Anda memiliki beberapa opsi:
- Mode khusus laporan (
Content-Security-Policy-Report-Only
) atau mode penerapan (Content-Security-Policy
). Dalam mode laporan saja, CSP tidak akan memblokir sumber daya, jadi tidak ada yang rusak di situs Anda, tetapi Anda dapat melihat kesalahan dan mendapatkan untuk hal-hal yang dapat diblokir. Secara lokal, saat Anda mengatur CSP Anda, ini tidak terlalu penting, karena kedua mode menunjukkan error di konsol browser. Jika ada, mode penerapan dapat membantu Anda menemukan sumber daya draf yang diblokir CSP Anda, karena memblokir sumber daya dapat membuat halaman tampak 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>
dapat lebih mudah untuk menyesuaikan CSP dan dengan cepat melihat bagaimana pengaruhnya terhadap situs Anda. Namun:- Nantinya, saat men-deploy CSP Anda di produksi, sebaiknya tetapkan sebagai header HTTP.
- Jika Anda ingin menetapkan CSP dalam mode laporan saja, Anda harus menyetelnya sebagai , karena tag meta CSP tidak mendukung mode laporan saja.
Tetapkan 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. Berbasis nonce CSP hanya dapat memitigasi XSS jika penyerang tidak dapat menebak nilai nonce. J 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 di 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 }); });
Tambahkan atribut nonce
ke elemen <script>
Dengan CSP berbasis nonce, setiap elemen <script>
harus
memiliki atribut nonce
yang cocok dengan nonce acak
yang ditentukan dalam header CSP. Semua skrip dapat memiliki
nonce. Langkah pertama adalah menambahkan
atribut-atribut ini ke semua skrip sehingga
CSP mengizinkannya.
Tetapkan 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, sintaksnya adalah sebagai berikut:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Memuat skrip yang bersumber secara dinamis
Karena {i>hash <i}CSP didukung di seluruh {i>browser<i} hanya untuk skrip {i>inline<i}, Anda harus memuat semua skrip pihak ketiga secara dinamis menggunakan skrip inline. Hash untuk skrip bersumber tidak didukung dengan baik di seluruh browser.
<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>
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
Pertimbangan pemuatan skrip
Contoh skrip inline menambahkan s.async = false
untuk memastikan
yang dijalankan foo
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, karena
untuk skrip async
. Namun, dengan cuplikan ini,
perhatikan:
-
Salah satu atau kedua skrip mungkin dieksekusi sebelum dokumen selesai
mengunduh. Jika Anda ingin dokumen tersebut sudah siap pada saat
skrip dijalankan, tunggu peristiwa
DOMContentLoaded
sebelum Anda menambahkan skrip. Jika ini menyebabkan masalah kinerja karena skrip tidak mulai didownload cukup awal, gunakan tag pramuat lebih awal di halaman. -
defer = true
tidak melakukan apa pun. Jika Anda memerlukannya , jalankan skrip secara manual jika 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. Ini berarti
penyerang yang menemukan {i>bug<i} XSS dapat
menyuntikkan HTML semacam ini dan mengeksekusi
pada JavaScript. CSP berbasis nonce atau hash melarang penggunaan markup semacam ini.
Jika situs Anda menggunakan salah satu pola ini, Anda harus memfaktorkan ulang pola tersebut agar lebih aman
alternatif.
Jika Anda mengaktifkan CSP di langkah sebelumnya, Anda akan dapat melihat pelanggaran CSP di konsol setiap kali CSP memblokir pola yang tidak kompatibel.
Dalam sebagian besar kasus, perbaikannya mudah dilakukan:
Memfaktorkan ulang pengendali peristiwa inline
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>
<span onclick="doThings();">A thing.</span>
Memfaktorkan ulang URI javascript:
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>
<a href="javascript:linkClicked()">foo</a>
Hapus eval()
dari JavaScript Anda
Jika aplikasi Anda menggunakan eval()
untuk mengonversi serialisasi string JSON menjadi 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 aturan berbasis nonce yang ketat
CSP, tetapi Anda harus menggunakan kata kunci CSP 'unsafe-eval'
, yang membuat
kebijakan yang agak kurang aman.
Anda dapat menemukan hal ini dan contoh pemfaktoran ulang tersebut lebih banyak dalam CSP ketat ini codelab:
Langkah 4 (Opsional): Tambahkan penggantian untuk mendukung versi browser lama
Jika Anda perlu mendukung versi browser lama:
- Penggunaan
strict-dynamic
memerlukan penambahanhttps:
sebagai penggantian untuk langkah sebelumnya versi Safari. Saat Anda melakukan hal ini:- Semua browser yang mendukung
strict-dynamic
mengabaikan penggantianhttps:
, jadi ini tidak akan mengurangi kekuatan kebijakan. - Di browser lama, skrip yang bersumber dari luar 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 yang sah di lingkungan pengembangan lokal, Anda bisa men-deploy CSP ke staging, lalu ke lingkungan produksi:
- (Opsional) Terapkan CSP Anda dalam mode khusus laporan menggunakan
Header
Content-Security-Policy-Report-Only
. Mode khusus laporan berguna untuk menguji perubahan yang berpotensi dapat menyebabkan gangguan seperti CSP baru dalam produksi sebelum Anda mulai menerapkan pembatasan CSP. Dalam mode laporan saja, 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 rusak bagi pengguna akhir Anda. Untuk selengkapnya informasi selengkapnya, lihat API Pelaporan. - Ketika Anda yakin bahwa CSP Anda tidak akan merusak situs Anda bagi pengguna akhir,
deploy CSP Anda menggunakan header respons
Content-Security-Policy
. Rab sarankan untuk menyetel CSP Anda menggunakan sisi server header HTTP karena lebih aman daripada tag<meta>
. Setelah Anda menyelesaikan langkah ini, CSP Anda akan mulai melindungi aplikasi Anda dari XSS.
Batasan
CSP yang ketat umumnya memberikan lapisan keamanan tambahan yang kuat yang membantu
memitigasi XSS. Dalam kebanyakan 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'
), di sana
dalam kasus saat CSP juga tidak melindungi aplikasi Anda:
- Jika Anda nonce skrip, tetapi ada injeksi langsung ke bagian isi atau
Parameter
src
dari elemen<script>
tersebut. - Jika ada injeksi ke lokasi skrip yang dibuat secara dinamis
(
document.createElement('script')
), termasuk ke dalam fungsi library mana pun yang membuatscript
node DOM berdasarkan nilai argumennya. Ini menyertakan beberapa API umum seperti.html()
jQuery, serta.get()
dan.post()
di jQuery < 3.0 - Apakah ada injeksi template dalam aplikasi AngularJS lama. Penyerang yang dapat menginjeksikan ke dalam template AngularJS dapat menggunakannya untuk eksekusi JavaScript arbitrer.
- Jika kebijakan berisi
'unsafe-eval'
, injeksi keeval()
,setTimeout()
, dan beberapa API lainnya yang jarang digunakan.
Pengembang dan insinyur keamanan harus memberi perhatian khusus pada yang sama selama peninjauan kode dan audit keamanan. Anda dapat menemukan detail lebih lanjut di kasus ini dalam Kebijakan Keamanan Konten: Masalah yang Berhasil antara Pengerasan dan Mitigasi.
Bacaan lebih lanjut
- CSP Sudah Mati, Panjang umur CSP! Mengenai Ketidakamanan Daftar Putih dan Kebijakan Masa Depan Keamanan Konten
- Evaluator CSP
- Konferensi LocoMoco: Kebijakan Keamanan Konten - Kekacauan yang terjadi antara hardening dan mitigasi
- Diskusi Google I/O: Mengamankan Aplikasi Web dengan Fitur Platform Modern