Langsung ke konten utama

Implementasi Content Security Policy

  • Terbit: 
  • Penulis: Angga

Apa itu content security policy? Kurang lebihnya adalah standar keamanan yang diimplementasikan via header dengan sistem whitelist untuk menghindari XSS, clickjacking, dan code injection di situs. Di antara semua proses pengamanan via manipulasi header, ini yang paling ampun riweuh implementasinya, dan dalam kasus-kasus tertentu, sama sekali tidak bisa diimplementasikan, atau jika beruntung, bisa dengan beberapa kompromi.

Jadi misalnya dengan implementasi header CSP dengan konfigurasi yang benar, maka hanya script-script dan style dari situs yang sudah di-whitelist yang hanya akan dimuat oleh peramban. Kekurangannya adalah inline script dan inline style yang tidak bisa dan sudah pasti ditolak oleh peramban kecuali menggunakan tuas 'unsafe-inline' yang membuat implementasinya menjadi tidak terlalu "aman".

Untuk mengatasi hal tersebut, maka dibutuhkan salah satu faktor terpenting dalam implementasi CSP: nonce, atau token sekali pakai yang digunakan oleh peramban untuk verifikasi keaslian "data", jadi untuk script-script inline tersebut bisa dimuat oleh peramban dan tidak diblok.

Generate dan Implementasi Nonce

Agar sistem bisa untuk generate nonce sendiri bisa via mod untuk Apache dengan nama mod_cspnonce. Namun, seperti yang pernah saya sebutkan sebelumnya, mualas, pak. Karena kebetulan menggunakan Cloudflare, kenapa tidak generate saja noncenya via worker-nya Cloudflare? Gratis hingga 100.000 requests/bulan.

Lagipula karena pakai Cloudflare ada beberapa script yang di-inject Cloudflare yang perlu di-whitelist dengan nonce juga (atau tambahin via URL whitelist di header, tapi mualaaaas).

Untuk itu saya hanya tinggal masuk ke Cloudflare saja, lalu buat worker baru yang saya isi dengan perintah yang bisa disalin-tempel ulang di repository Github ini. Simpan workernya, dan assign untuk https://angga.win, jangan dipasang dengan trailingnya ya, cukup base domainnya saja.

Dengan menggunakan perintah yang di atas, maka seluruh script-script yang di-inject oleh Cloudflare, seperti untuk email obfuscation, juga sudah ikut dapat noncenya, jadi sekali jalan, praktis, tinggal implementasi dengan mengikuti tutorial yang ada di halaman Github yang ada di atas dengan menambahkan nonce="DhcnhD3khTMePgXw" ke inline script, inline style, 3rd party script, atau 3rd party style yang nantinya akan langsung diubah oleh worker ke token nonce yang sebenarnya.

Contoh:

link rel="stylesheet" href="situsketiga.ext/styles.css" nonce="DhcnhD3khTMePgXw"

Yang akan diubah otomatis oleh worker dengan token sekali pakai nantinya, misal akan menjadi: 

link rel="stylesheet" href="situsketiga.ext/styles.css" nonce="ZDAzYmM4Zjk1ZQ/ZTJjNGUwNDQ5NTI4NmQ="

Atur Asynchronous CSS

Kelemahan terbesar CSP adalah belum adanya dukungan untuk inline handler tanpa lagi-lagi menambahkan 'unsafe-inline' di implementasi CSP-nya, dengan kata lain, implementasi terbaik asynchronous CSS dengan inline handler onload tidak lagi berjalan sempurna tanpa tuas 'unsafe-inline'.

Jadi satu-satunya cara agar asynchronous CSS dapat tetap diimplementasikan adalah dengan pertama-tama menyatakannya sebagai media="print" agar peramban tidak memuatnya, lalu mengubahnya menjadi media="all" dengan kait class saat situs terpanggil utuh, via Javascript.

Jadi, pertama-tama panggil dahulu CSS-nya, misal:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/picnic/7.1.0/picnic.min.css" integrity="sha512-HZgZOfcUw1rxWuEBlzDis5U4HlbzR0wcWmb3FrLSKV6uhZiZpT9JSTzPJplHDmJZJFNfAReW+iDELJ1kADYHtA==" crossorigin="anonymous" referrerpolicy="no-referrer" media="print" class="asyncCSP" nonce="DhcnhD3khTMePgXw" />

Lalu buat scriptnya di akhir dokumen untuk mengubah media="print" menjadi media="all" via kait class asyncCSP saat situs sudah selesai dimuat penuh.

<script nonce="DhcnhD3khTMePgXw">
    document.addEventListener("DOMContentLoaded", () => {
        document.querySelectorAll(".asyncCSP").forEach(el => {
            el.media = "all"
            console.log(`Loaded: ${el.href}`)
        })
    });
</script>

Silakan hapus yang baris console.log-nya apabila sudah selesai develop. Saya pribadi tidak menghapusnya karena perasaan cemas yang berlebihan, jadi kadang suka meriksa ke konsol apakah seluruh library-library-nya sudah berhasil termuat, padahal tahu sudah pasti termuat, wk.

Perhatian!

Cara pemanggilan asynchronous CSS seperti di atas akan mengakibatkan content shift yang cukup lumayan saat web dipanggil. Disarankan untuk menggunakan inline critical CSS untuk menghindari content shift yang signifikan.

Saya sendiri pakai, tapi masih ada content shift yang cukup signifikan selama beberapa saat web dipanggil pertama kali, namun akan kembali normal untuk pemanggilan berikutnya sampai cache di disk dihapus untuk waktu berkala berikutnya.

Solusi? Debug manual inline critical CSS-nya sampai hilang content shift-nya saat pertama kali memuat situs, tapi yawda deh mualas, pak.

Implementasi CSP

Ada beberapa cara implementasi CSP dengan implementasi via header webserver sebagai salah satu solusi terbaik, misalnya apabila menggunakan Apache bisa via dengan menambahkan header di .htaccess-nya. Namun karena saat ini menggunakan Bludit dan di halaman admin Bludit sendiri inline script dan inline style-nya cukup banyak, tentu implementasi via header webserver ini yang bersifat global akan menjadi masalah sendiri.

Kecuali misalnya rela ngedit fail-fail di halaman adminnya satu-satu buat nambahin nonce-nya setiap update versi Bludit sih yaaa, silakan ya, wk.

Cara yang untuk saya pribadi paling cocok adalah dengan menambahkan header via php, satu untuk yang di front-end, dan satu lain untuk yang back-end-nya Bludit. Satu-satunya cara ya harus kompromi lagi sih.

Contoh yang saya pakai di front-end situs saya via fail header theme-nya:

<?php
    header("Content-Security-Policy: default-src 'strict-dynamic' 'unsafe-inline' https: 'nonce-DhcnhD3khTMePgXw'; font-src 'self' https://fonts.googleapis.com/ https://fonts.gstatic.com https://cdnjs.cloudflare.com 'nonce-DhcnhD3khTMePgXw'; img-src 'self' data:; connect-src 'self' https://fonts.googleapis.com/ https://fonts.gstatic.com 'nonce-DhcnhD3khTMePgXw'; style-src 'self' https://fonts.googleapis.com/ https://fonts.gstatic.com https://cdnjs.cloudflare.com 'nonce-DhcnhD3khTMePgXw'; base-uri 'self'; object-src 'none'; script-src-elem 'self' 'nonce-DhcnhD3khTMePgXw';");
?>

'unsafe-inline' di situ hanya untuk backward compatibility saja ya untuk peramban yang lama, karena sudah pakai 'strict-dynamic' untuk default-src-nya, jadi kalau ada nonce misalnya, 'unsafe-inline'-nya di-bypass di peramban baru. Sementara untuk source-source yang di-whitelist-nya itu saya main salin tempel aja dari satu ke yang lainnya karena malas ya, jangan ditiru, wakakaka.

Misal, di font-src itu ga perlu ada dua-duanya fonts.googleapis sama fonts.gstatic, cuma butuh yang gstatic, tapi males ah, kopas-aeeee biar seragam semua.

Pengecualian untuk img-src karena saya ada menggunakan inline svg, jadi harus ditambah data:.

Kemudian untuk yang di back-end bluditnya bisa diedit di fail login.php dan index.php yang ada di folder themes untuk admin, saat ini hanya theme "booty." Isinya hampir mirip dengan yang di atas, tapi cuma 'self' dan 'unsafe-inline' saja toggle-nya sama ada beberapa data: untuk inline font dan svg.

Contohnya:

<?php
    header("Content-Security-Policy: default-src 'self'; font-src 'self' data: 'unsafe-inline'; img-src 'self' data:; connect-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; base-uri 'self'; object-src 'none'; script-src 'self' 'unsafe-inline';");
 ?>

Perhatian!

Jangan salin dan tempel saja contoh implementasi CSP di atas ya, karena tiap orang, tiap situs, kebutuhan untuk whitelist-nya beda-beda, jadi harus benar-benar dicek satu per-satu via konsol di perambannya apakah semua resources sudah terload dengan sempurna. Emang repot sih, tapi ya begitu lah implementasi CSP.

Oiya, yang https: doang untuk default-src seperti yang saya buat di atas untuk backward compatibilitynya itu ga aman ya. Itu karena mualas aja satu-satu masukin lagi sources-nya, alias saya goblo, jangan ditiru.

Tes!

Jangan lupa untuk selalu cek konsol perambannya untuk melihat apakah ada script atau styles, atau resource apapun yang terblok oleh Content Security Policy-nya dan edit kembali headernya sesuai dengan kebutuhan. Berikut adalah hasil tes untuk implementasi saya untuk content security policy di angga.win via CSP Evaluator.

Gambar adalah tangkapan layar hasil test website saya di csp evaluator dengan hasil semua centang hijau dan lambang perisai csp berubah menjadi hijau

Hijau-hijau mas broooo, wakakaka!

Dan tentu saja karena CSP yang paling ribet sudah teratasi, nilai di securityheaders.com juga sudah A+.

Hasil testing di Security Headers Probely mendapatkan nilai A+

Untuk backend-nya ya yang jelas di CSP Evaluator dapat perisai merah, wakakaka!

Kesimpulan

Ribet dan ngeselin parah sih, wk. Menurut saya pribadi, karena saya orangnya mungkin terlalu konservatif kali ya (atau memang terlalu nubi...), saya rasa lebih baik menghabiskan waktu pertama kali untuk keamanan yang paling dasar dulu sih, seperti konfigurasi SSH agar bisa masuk hanya bisa dengan keypair saja, ubah port SSH, lalu setup mod_security/WAF dengan rules, terserah mau rules yang mana aja, boleh Comodo, boleh OWASP, boleh yang lain, dan ya testing rules mod_security-nya dan whitelist seperlunya sesuai kebutuhan.

Menurut saya itu yang paling dasar, bisa dilanjut dengan setup firewall dan lain-lain selanjutnya.

Kalau misalnya masih ada energi untuk setup security header seperti CSP lagi berikutnya yaa lebih bagus dan mantap.

Saya sendiri bukan ahli keamanan atau gimana-gimana juga sih, jadi kayanya juga ga ada authority kalau ngomongin masalah security, jadi maaf apabila blog-nya malah menyesatkan, wk. Cuma ya, seneng aja gitu ngeliat hijau-hijau setelah sebelumnya gagal implementasi CSP di situs klien karena konfigurasi-nya tabrakan, wakakaka.