Ketika serangan XSS berhasil menyisipkan JavaScript ke halaman website, masalahnya bukan hanya tampilan yang berubah. Script tersebut bisa mencoba membaca data di halaman, mengubah formulir, mengarahkan pengunjung ke situs palsu, atau menjalankan aksi atas nama pengguna yang sedang login.
Validasi input dan escaping tetap menjadi pertahanan utama. Namun, website juga membutuhkan lapisan tambahan jika ada celah yang terlewat. Di sinilah Content Security Policy atau CSP berguna: sebuah kebijakan yang dikirim server kepada browser untuk menentukan script, gambar, frame, dan koneksi mana yang boleh digunakan halaman.
CSP bukan pengganti secure coding
CSP sebaiknya dipahami seperti rem darurat, bukan pengganti mesin yang dirawat. Jika kode aplikasi masih mencetak input pengguna tanpa escaping, celah XSS tetap harus diperbaiki di sumbernya. CSP membantu membatasi dampak ketika ada script yang tidak semestinya mencoba masuk atau berjalan.
Contohnya, sebuah halaman hanya boleh menjalankan script yang diberi nonce. Nonce adalah nilai acak yang dibuat server untuk setiap respons. Browser hanya menjalankan tag <script> yang memiliki nonce yang sama dengan nilai di header CSP.
Mengapa allowlist sering tidak cukup
Pendekatan lama biasanya membuat daftar domain yang dipercaya, misalnya mengizinkan script dari domain sendiri dan beberapa layanan pihak ketiga. Masalahnya, daftar ini mudah menjadi terlalu longgar. Satu domain yang dipercaya dapat memuat script tambahan, berpindah endpoint, atau memiliki konfigurasi yang tidak sepenuhnya berada dalam kendali kita.
Pendekatan yang lebih ketat menggunakan nonce atau hash untuk menentukan script tertentu yang boleh berjalan. OWASP dan MDN sama-sama menjelaskan nonce-based atau hash-based CSP sebagai pola yang lebih kuat dibanding sekadar menambahkan banyak domain ke daftar izin.
Memilih nonce atau hash
Nonce untuk halaman dinamis
Nonce cocok jika server membuat halaman secara dinamis. Nilainya harus acak, sulit ditebak, dan dibuat ulang pada setiap respons.
Content-Security-Policy: script-src 'nonce-RANDOM_VALUE' 'strict-dynamic'; object-src 'none'; base-uri 'none'Tag script yang memang dibuat aplikasi kemudian memakai nilai yang sama:
<script nonce="RANDOM_VALUE" src="/assets/app.js"></script>Hal pentingnya bukan sekadar menambahkan atribut nonce ke semua tag script. Nonce harus diberikan hanya kepada script yang memang dipercaya. Jika sistem otomatis menambahkan nonce ke seluruh tag, script yang disisipkan penyerang bisa ikut mendapatkan izin.
Hash untuk script yang isinya tetap
Hash lebih praktis untuk inline script yang jarang berubah. Browser menghitung hash dari isi script dan hanya menjalankannya jika hasilnya cocok dengan hash di kebijakan CSP.
Content-Security-Policy: script-src 'sha256-HASH_SCRIPT'; object-src 'none'; base-uri 'none'Kekurangannya, perubahan kecil—termasuk spasi atau pemformatan—dapat mengubah hash. Karena itu, hash perlu diperbarui setiap kali isi script berubah.
Mulai dengan mode Report-Only
Kesalahan paling umum saat menerapkan CSP adalah langsung mengaktifkan kebijakan ketat di website produksi. Akibatnya, fitur pembayaran, analytics, widget komentar, atau editor halaman tiba-tiba tidak berfungsi.
Gunakan header Content-Security-Policy-Report-Only terlebih dahulu. Dalam mode ini browser mencatat pelanggaran, tetapi tidak memblokir resource. Periksa laporan di Developer Tools, lalu catat script mana yang benar-benar dibutuhkan.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-RANDOM_VALUE'; object-src 'none'; base-uri 'none'Setelah daftar dependensi dipahami dan error penting sudah dibereskan, barulah kebijakan dapat dipindahkan ke header Content-Security-Policy.
Langkah praktis untuk website PHP
- Inventarisasi script. Catat script internal, CDN, analytics, chat widget, pembayaran, dan integrasi lain yang dimuat halaman.
- Kurangi inline JavaScript. Pindahkan kode ke file eksternal bila memungkinkan. Ini membuat aturan lebih mudah dirawat dan diuji.
- Buat nonce dengan generator aman. Dalam PHP, gunakan fungsi seperti
random_bytes(), bukan angka acak biasa atau timestamp. - Kirim nonce lewat template. Nilai yang sama harus digunakan di header dan pada tag script yang memang diizinkan.
- Uji dengan Report-Only. Buka halaman penting: login, checkout, formulir, dashboard, dan halaman dengan widget pihak ketiga.
- Aktifkan bertahap. Mulai dari halaman yang paling mudah dikontrol sebelum menerapkan kebijakan ke seluruh situs.
Contoh sederhana di PHP:
<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: script-src 'nonce-$nonce'; object-src 'none'; base-uri 'none'");
?>
<script nonce="<?php echo htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8'); ?>">
initializeApp();
</script>Contoh tersebut masih perlu disesuaikan dengan arsitektur aplikasi, cache, dan script pihak ketiga yang digunakan. Jika halaman HTML disimpan oleh cache sebagai file statis, nonce per respons menjadi lebih sulit diterapkan dan pendekatan hash mungkin lebih sesuai.
Bagaimana dengan WordPress?
WordPress dan plugin dapat menambahkan script dari banyak sumber. Karena itu, menerapkan CSP secara global tanpa audit sering menimbulkan konflik. Mulailah dari halaman yang paling penting, periksa laporan pelanggaran, lalu evaluasi plugin yang menyisipkan inline script atau memanggil domain eksternal.
Jangan menyelesaikan semua error dengan menambahkan 'unsafe-inline' atau allowlist domain sebanyak mungkin. Keduanya memang dapat membuat fitur kembali berjalan, tetapi juga mengurangi manfaat perlindungan CSP.
Jika WordPress terhubung dengan sistem eksternal melalui REST API, gunakan kredensial khusus integrasi, seperti Application Passwords, bukan password utama pengguna. Kredensial tersebut dapat dibuat per aplikasi dan dicabut tanpa mengganti password utama.
Apa artinya bagi kita?
CSP paling berguna ketika diperlakukan sebagai bagian dari proses pengembangan, bukan aksesori keamanan yang dipasang sekali lalu dilupakan. Setiap plugin baru, tag manager, widget, dan perubahan frontend dapat mengubah kebutuhan kebijakan.
Checklist singkat yang bisa dilakukan sekarang:
- Periksa apakah website memiliki header CSP.
- Uji halaman penting dengan mode Report-Only.
- Identifikasi inline script yang masih digunakan.
- Hapus dependensi pihak ketiga yang tidak lagi diperlukan.
- Pastikan input tetap di-escape dan query database tetap menggunakan prepared statement.
- Dokumentasikan alasan setiap domain yang masuk ke kebijakan.
Tujuan akhirnya bukan membuat header yang paling panjang, melainkan membuat aturan yang cukup ketat untuk membatasi script tak dikenal dan cukup teruji agar website tetap berfungsi. Dengan pendekatan bertahap, CSP dapat menjadi lapisan pertahanan yang masuk akal—bukan sumber masalah baru.
Sumber & bacaan lebih lanjut
- Content Security Policy Cheat Sheet - OWASP
- Content Security Policy (CSP) - MDN Web Docs
- Content Security Policy implementation - MDN Web Docs
- Authentication - WordPress REST API Handbook
- Application Passwords - WordPress Advanced Administration Handbook
– Rio Yotto @rioyotto
