Keamanan

Content Security Policy (CSP): Panduan Lengkap

Pelajari Content Security Policy (CSP) secara mendalam β€” mulai dari konsep dasar, direktif-direktif penting, cara kerja nonce dan hash, hingga implementasi reporting untuk membangun pertahanan berlapis terhadap serangan XSS dan injection di aplikasi web modern

1. Pengenalan Content Security Policy

Content Security Policy (CSP) adalah mekanisme keamanan berbasis HTTP header yang dirancang untuk melindungi aplikasi web dari serangan Cross-Site Scripting (XSS), clickjacking, dan berbagai jenis injection attack lainnya. CSP bekerja dengan cara mendefinisikan whitelist β€” daftar sumber konten yang diizinkan untuk dimuat dan dieksekusi oleh browser.

Ketika sebuah halaman web tidak memiliki CSP, browser secara default mengizinkan konten dari sumber mana saja β€” termasuk skrip inline, eval, dan resource dari domain pihak ketiga yang mungkin berbahaya. CSP memberikan kontrol penuh kepada developer untuk menentukan dari mana browser boleh memuat resource.

Mengapa CSP Penting?

Alasan Penjelasan
Mitigasi XSSCSP membatasi skrip yang bisa dieksekusi, sehingga attacker tidak bisa menjalankan skrip berbahaya meskipun berhasil menyuntikkannya ke halaman
Perlindungan InjectionMemblokir eksekusi skrip dari sumber yang tidak terpercaya, mengurangi dampak dari SQL injection dan DOM injection
Clickjacking PreventionDengan direktif frame-ancestors, CSP bisa mencegah halaman dimuat di dalam iframe milik attacker
Data ExfiltrationDirektif connect-src membatasi ke mana data bisa dikirim, mencegah attacker mengirim data curian ke server mereka
ComplianceBanyak standar keamanan dan regulasi (PCI-DSS, HIPAA) merekomendasikan atau mewajibkan implementasi CSP

Sejarah CSP

CSP pertama kali diperkenalkan pada tahun 2012 oleh Mozilla sebagai proposal untuk mengatasi masalah XSS yang terus menjadi ancaman utama keamanan web. Seiring waktu, CSP berevolusi dari Level 1 ke Level 3, menambahkan fitur-fitur seperti nonce, hash, dan reporting API. Saat ini, CSP didukung oleh semua browser modern termasuk Chrome, Firefox, Safari, dan Edge.

Diagram: Cara Kerja CSP dalam Browser
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  BROWSER (Client)                       β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚  β”‚  HTML Page   │────▢│ CSP Header   β”‚                  β”‚
β”‚  β”‚  dimuat      β”‚     β”‚ diterima     β”‚                  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜                  β”‚
β”‚                             β”‚                           β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚                    β”‚  Policy Parser  β”‚                  β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β”‚
β”‚                             β”‚                           β”‚
β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚         β–Ό                   β–Ό                   β–Ό      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Inline Scriptβ”‚   β”‚ External JS  β”‚   β”‚  Images/   β”‚  β”‚
β”‚  β”‚ ❌ Diblokir  β”‚   β”‚ βœ… Diizinkan β”‚   β”‚  CSS βœ…    β”‚  β”‚
β”‚  β”‚ (tanpa nonce)β”‚   β”‚ (dari origin β”‚   β”‚ (sesuai    β”‚  β”‚
β”‚  β”‚              β”‚   β”‚  yang benar) β”‚   β”‚  policy)   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚           CSP Violation Report                   β”‚   β”‚
β”‚  β”‚  β†’ Dikirim ke report-uri/report-to endpoint     β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Cara Kerja CSP

CSP diimplementasikan melalui HTTP header yang dikirim oleh server ke browser bersama dengan response halaman web. Browser kemudian menganalisis header tersebut dan menerapkan aturan yang didefinisikan sebelum memuat dan mengeksekusi resource apa pun.

Dua Cara Mengirim CSP

Contoh: CSP via HTTP Header
# Cara 1: Via HTTP Header (Direkomendasikan)
# Ditambahkan di konfigurasi server (Nginx, Apache, dll)

# Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;

# Apache
Header set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"

# Node.js / Express
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy',
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';");
  next();
});

# ============================================

# Cara 2: Via HTML Meta Tag (Kurang Fleksibel)
# <meta http-equiv="Content-Security-Policy" 
#   content="default-src 'self'; script-src 'self';">

# Catatan: Tidak semua direktif didukung via meta tag,
# misalnya report-uri dan frame-ancestors.

Proses Evaluasi CSP

Setiap kali browser ingin memuat resource (script, image, stylesheet, font, dll), browser melakukan pengecekan terhadap policy yang berlaku:

  1. Browser menerima HTTP response yang mengandung header Content-Security-Policy
  2. Policy di-parse dan disimpan dalam memori browser
  3. Ketika ada resource yang akan dimuat, browser memeriksa apakah origin resource tersebut diizinkan oleh direktif yang sesuai
  4. Jika resource diizinkan β†’ resource dimuat normal
  5. Jika resource TIDAK diizinkan β†’ resource diblokir dan violation report dikirim (jika reporting dikonfigurasi)
⚠️ Perbedaan CSP dan CSP-Report-Only
  • Content-Security-Policy β€” Menerapkan policy dan memblokir resource yang melanggar
  • Content-Security-Policy-Report-Only β€” Hanya melaporkan pelanggaran tanpa memblokir, sangat berguna untuk testing sebelum enforce

3. Direktif-Direktif CSP

CSP memiliki banyak direktif yang masing-masing mengontrol jenis resource tertentu. Memahami setiap direktif adalah kunci untuk membuat policy yang efektif namun tidak memecah fungsionalitas website.

Daftar Direktif CSP Lengkap

Direktif Fungsi Contoh Nilai
default-srcFallback untuk semua direktif resource yang tidak didefinisikan secara eksplisit'self'
script-srcMenentukan sumber JavaScript yang diizinkan'self' 'nonce-abc123'
style-srcMenentukan sumber CSS yang diizinkan'self' 'unsafe-inline'
img-srcMenentukan sumber gambar yang diizinkan'self' data: https:
font-srcMenentukan sumber font yang diizinkan'self' fonts.gstatic.com
connect-srcMenentukan target untuk XMLHttpRequest, fetch, WebSocket'self' api.example.com
frame-srcMenentukan sumber frame/iframe yang diizinkan'self' https://www.youtube.com
frame-ancestorsMenentukan siapa yang boleh meng-embed halaman dalam iframe'self'
media-srcMenentukan sumber audio dan video yang diizinkan'self' cdn.example.com
object-srcMenentukan sumber plugin (Flash, Java) yang diizinkan'none'
base-uriMenentukan URL base yang valid untuk tag <base>'self'
form-actionMenentukan target form submission yang diizinkan'self'
worker-srcMenentukan sumber Web Worker dan Service Worker'self'
manifest-srcMenentukan sumber application manifest'self'
prefetch-srcMenentukan sumber yang boleh di-prefetch/pre-render'self'
navigate-toMenentukan tujuan navigasi yang diizinkan (experimental)'self' example.com

Keyword Values

Keyword Arti Contoh Penggunaan
'self'Mengizinkan resource dari origin yang sama (scheme + host + port)script-src 'self'
'none'Tidak mengizinkan resource sama sekaliobject-src 'none'
'unsafe-inline'Mengizinkan inline script dan style (TIDAK direkomendasikan)script-src 'unsafe-inline'
'unsafe-eval'Mengizinkan penggunaan eval() dan sejenisnyascript-src 'unsafe-eval'
'strict-dynamic'Mempercayai script yang dimuat oleh script trusted lainnyascript-src 'strict-dynamic' 'nonce-xxx'
'unsafe-hashes'Mengizinkan event handler spesifik berdasarkan hashscript-src 'unsafe-hashes' 'sha256-xxx'

Contoh Policy untuk Berbagai Skenario

Contoh: Policy Dasar untuk Website Statis
# Policy ketat untuk website statis tanpa JavaScript eksternal
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'
Contoh: Policy untuk SPA dengan API Backend
# Single Page Application dengan REST API
Content-Security-Policy: \
  default-src 'self'; \
  script-src 'self' 'nonce-r4nd0m'; \
  style-src 'self' 'unsafe-inline'; \
  img-src 'self' https://cdn.example.com data:; \
  font-src 'self' https://fonts.gstatic.com; \
  connect-src 'self' https://api.example.com wss://ws.example.com; \
  frame-src 'none'; \
  object-src 'none'; \
  base-uri 'self'; \
  form-action 'self'
Contoh: Policy untuk Website dengan CDN
# Website yang menggunakan CDN untuk static assets
Content-Security-Policy: \
  default-src 'self'; \
  script-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; \
  style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; \
  img-src 'self' https://*.cloudfront.net https://images.unsplash.com data:; \
  font-src 'self' https://fonts.gstatic.com; \
  connect-src 'self'; \
  media-src 'self' https://*.cloudfront.net; \
  frame-ancestors 'none'

4. Nonce dan Hash

Salah satu tantangan terbesar dalam implementasi CSP adalah menangani inline script β€” script yang ditulis langsung di dalam tag HTML. Menggunakan 'unsafe-inline' bukan solusi karena akan mengalahkan tujuan utama CSP. Di sinilah nonce dan hash berperan.

Apa Itu Nonce?

Nonce (Number Used Once) adalah string acak yang dihasilkan secara kriptografis untuk setiap request. Nonce ditambahkan ke CSP header dan juga ke tag script/style yang diizinkan. Browser hanya akan mengeksekusi script yang memiliki nonce yang cocok dengan yang ada di header.

Contoh: Implementasi Nonce
# Langkah 1: Server menghasilkan nonce unik untuk setiap request
# (Di Node.js/Express)
const crypto = require('crypto');

app.use((req, res, next) => {
  // Generate nonce random 128-bit, encode base64
  const nonce = crypto.randomBytes(16).toString('base64');
  
  // Simpan nonce di res.locals agar bisa diakses di template
  res.locals.nonce = nonce;
  
  // Set CSP header dengan nonce
  res.setHeader('Content-Security-Policy', 
    "script-src 'nonce-" + nonce + "' 'strict-dynamic'; " +
    "style-src 'self' 'nonce-" + nonce + "'; " +
    "default-src 'self'; " +
    "object-src 'none'; " +
    "base-uri 'self'"
  );
  next();
});

# ============================================

# Langkah 2: Di template HTML, tambahkan nonce ke tag script
# <script nonce="<%= nonce %>">
#   // Inline script yang diizinkan
#   console.log('Script ini diizinkan oleh CSP');
# </script>

# Script TANPA nonce akan DIBLOKIR oleh browser:
# <script>
#   // Script ini TIDAK akan dieksekusi
#   alert('Ini diblokir!');
# </script>

# ============================================

# Konfigurasi Nginx dengan variable untuk nonce
# generate_nonce.conf
perl_set $csp_nonce 'use Digest::MD5 qw(md5_hex);
  md5_hex(time() . rand())';

server {
  add_header Content-Security-Policy 
    "script-src 'nonce-$csp_nonce' 'strict-dynamic'; default-src 'self'" always;
}

Apa Itu Hash?

Hash (Subresource Integrity Hash) bekerja dengan cara yang berbeda dari nonce. Alih-alih string acak, hash dihitung dari konten script itu sendiri. Browser menghitung hash dari konten script yang ditemukan di halaman dan membandingkannya dengan hash yang didefinisikan di CSP header.

Contoh: Implementasi Hash
# Menghitung hash SHA-256 dari konten script
# Contoh script:
# <script>alert('Hello World');</script>

# Cara menghitung hash:
# echo -n "alert('Hello World');" | openssl dgst -sha256 -binary | openssl base64
# Output: qUqP5cyxm6YcTAhz05Hph5gvu9M=

# Atau menggunakan Node.js:
const crypto = require('crypto');
const scriptContent = "alert('Hello World');";
const hash = crypto.createHash('sha256')
  .update(scriptContent)
  .digest('base64');
// hash = 'qUqP5cyxm6YcTAhz05Hph5gvu9M='

# CSP Header dengan hash:
Content-Security-Policy: script-src 'sha256-qUqP5cyxm6YcTAhz05Hph5gvu9M='

# ============================================

# Kalkulasi hash untuk beberapa script:
# Script 1: document.getElementById('app').innerHTML = 'Loaded';
# Hash: sha256-xTc7sChFfZVR+d9jBhpCKBR7a8goKjP2NqFW2HKBsEM=

# Script 2: window.dataLayer = window.dataLayer || [];
# Hash: sha256-Gg/IBtfCFBWkZAijOBaMGWm3FkSJKSMr+YOyhLBrJq8=

# Gabungan:
Content-Security-Policy: script-src 'sha256-xTc7sChFfZVR+d9jBhpCKBR7a8goKjP2NqFW2HKBsEM=' 'sha256-Gg/IBtfCFBWkZAijOBaMGWm3FkSJKSMr+YOyhLBrJq8='

# ============================================

# Kelebihan Hash vs Nonce:
# βœ… Hash = deterministik, bisa di-cache
# βœ… Hash = tidak perlu generate per-request
# βœ… Hash = lebih aman (tidak bisa diprediksi attacker)
# ❌ Hash = harus dihitung ulang jika konten script berubah
# ❌ Hash = tidak praktis untuk banyak inline script

Perbandingan Nonce vs Hash

Aspek Nonce Hash
GenerasiRandom per-requestDihitung dari konten script
CachingSulit (berbeda setiap request)Mudah (selalu sama untuk konten yang sama)
KeamananBergantung pada randomnessLebih deterministik
KemudahanMudah diimplementasikanPerlu kalkulasi hash
Dynamic ContentCocok untuk konten dinamisCocok untuk konten statis
RekomendasiDengan 'strict-dynamic'Untuk script yang jarang berubah

'strict-dynamic' dan Hierarki Trust

Keyword 'strict-dynamic' memungkinkan script yang sudah trusted (memiliki nonce/hash yang valid) untuk memuat script lain secara dinamis. Ini sangat berguna untuk framework JavaScript yang perlu memuat module secara runtime.

Diagram: strict-dynamic Trust Hierarchy
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              STRICT-DYNAMIC FLOW                  β”‚
β”‚                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ <script nonce="abc123">             β”‚         β”‚
β”‚  β”‚   // Script dengan nonce VALID βœ…    β”‚         β”‚
β”‚  β”‚   const s = document.createElement  β”‚         β”‚
β”‚  β”‚     ('script');                     β”‚         β”‚
β”‚  β”‚   s.src = '/app-bundle.js';         β”‚         β”‚
β”‚  β”‚   document.body.appendChild(s);     β”‚         β”‚
β”‚  β”‚ </script>                           β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚                 β”‚                                  β”‚
β”‚                 β–Ό                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ /app-bundle.js                      β”‚         β”‚
β”‚  β”‚ βœ… Diizinkan karena dimuat oleh     β”‚         β”‚
β”‚  β”‚    script yang memiliki nonce       β”‚         β”‚
β”‚  β”‚    (parent script terpercaya)       β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚                 β”‚                                  β”‚
β”‚                 β–Ό                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ /plugin.js (dimuat oleh app-bundle) β”‚         β”‚
β”‚  β”‚ βœ… Juga diizinkan (transitive trust)β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚  β”‚ <script src="/evil.js"></script>    β”‚         β”‚
β”‚  β”‚ ❌ DIBLOKIR β€” tanpa nonce dan       β”‚         β”‚
β”‚  β”‚    bukan dimuat oleh trusted script β”‚         β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

5. CSP Reporting

Salah satu fitur paling powerful dari CSP adalah kemampuannya untuk melaporkan pelanggaran policy. Dengan reporting, Anda bisa mengetahui ketika ada resource yang diblokir β€” baik itu serangan nyata atau resource legitimate yang perlu ditambahkan ke whitelist.

Dua Mekanisme Reporting

Mekanisme Status Deskripsi
report-uriDeprecatedMekanisme lama β€” menentukan URL endpoint untuk menerima violation reports
report-toModern (Level 3)Mekanisme baru β€” menggunakan Reporting API dengan named endpoint groups
Contoh: Implementasi CSP Reporting
# ============================================
# Konfigurasi Reporting dengan Reporting API
# ============================================

# 1. Definisikan reporting endpoint (di HTTP header Reporting-Endpoints)
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"

# 2. Set CSP header dengan report-to
Content-Security-Policy: \
  default-src 'self'; \
  script-src 'self' 'nonce-abc123'; \
  style-src 'self' 'unsafe-inline'; \
  report-to csp-endpoint

# ============================================
# Report-Only Mode (untuk testing)
# ============================================
Content-Security-Policy-Report-Only: \
  default-src 'self'; \
  script-src 'self' 'nonce-abc123'; \
  report-to csp-endpoint

# ============================================
# Contoh Violation Report (JSON yang dikirim browser)
# ============================================
{
  "type": "csp-violation",
  "age": 10,
  "url": "https://example.com/page",
  "user_agent": "Mozilla/5.0 ...",
  "body": {
    "documentURL": "https://example.com/page",
    "referrer": "",
    "blockedURL": "https://evil.com/malicious.js",
    "violatedDirective": "script-src-elem",
    "effectiveDirective": "script-src",
    "originalPolicy": "script-src 'self' 'nonce-abc123'",
    "disposition": "enforce",
    "statusCode": 200,
    "sample": ""
  }
}

# ============================================
# Server-side: Menerima dan memproses reports
# (Node.js/Express)
# ============================================
const express = require('express');
const app = express();

app.post('/csp-reports', express.json({ type: 'application/csp-report' }), (req, res) => {
  const report = req.body;
  
  console.log('[CSP VIOLATION]', {
    url: report.body?.documentURL,
    blocked: report.body?.blockedURL,
    directive: report.body?.violatedDirective,
    policy: report.body?.originalPolicy,
    timestamp: new Date().toISOString()
  });
  
  // Simpan ke database untuk analisis
  // db.saveViolation(report);
  
  res.status(204).end();
});

# Untuk report-to, server menerima array:
app.post('/csp-reports', express.json({ type: 'application/reports+json' }), (req, res) => {
  const reports = req.body; // Array of reports
  reports.forEach(report => {
    console.log('[CSP]', report.body.blockedURL);
  });
  res.status(204).end();
});
πŸ’‘ Tips CSP Reporting
  • βœ… Mulai dengan Content-Security-Policy-Report-Only sebelum enforce
  • βœ… Monitor reports selama 1-2 minggu untuk menemukan resource legitimate yang perlu di-whitelist
  • βœ… Gunakan layanan seperti Report URI (report-uri.com) untuk analisis yang lebih mudah
  • βœ… Filter noise dari reports β€” browser extension dan malware bisa menghasilkan false positives
  • βœ… Set up alerting untuk spike tiba-tiba dalam violation reports (indikasi serangan)

6. Implementasi CSP

Implementasi CSP yang baik memerlukan perencanaan dan testing yang cermat. Melompat langsung ke policy yang ketat bisa memecah fungsionalitas website. Berikut adalah panduan step-by-step untuk mengimplementasikan CSP secara bertahap.

Langkah 1: Audit Resource yang Digunakan

Contoh: Script Audit Resource
// Jalankan di browser console untuk mendeteksi semua resource
// yang dimuat oleh halaman

const resources = performance.getEntriesByType('resource');
const origins = new Set();

resources.forEach(r => {
  try {
    const url = new URL(r.name);
    origins.add(url.origin);
    console.log(`[${r.initiatorType}] ${r.name}`);
  } catch(e) {
    console.log(`[${r.initiatorType}] ${r.name} (inline)`);
  }
});

console.log('\n=== Unique Origins ===');
origins.forEach(o => console.log(o));

// Output contoh:
// [script] https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js
// [link] https://fonts.googleapis.com/css2?family=Inter
// [img] https://images.unsplash.com/photo-123
// [xmlhttprequest] https://api.example.com/v1/data
// === Unique Origins ===
// https://cdn.jsdelivr.net
// https://fonts.googleapis.com
// https://fonts.gstatic.com
// https://images.unsplash.com
// https://api.example.com

Langkah 2: Membuat Strict CSP (Modern Approach)

Pendekatan modern yang direkomendasikan oleh Google dan web security experts menggunakan kombinasi nonce + 'strict-dynamic' tanpa domain whitelist.

Contoh: Modern Strict CSP Template
# Strict CSP Template (Recommended)
# Tidak perlu whitelist domain β€” gunakan nonce + strict-dynamic

Content-Security-Policy: \
  default-src 'self'; \
  script-src 'self' 'nonce-{SERVER_GENERATED_NONCE}' 'strict-dynamic'; \
  style-src 'self' 'unsafe-inline'; \
  img-src 'self' https: data:; \
  font-src 'self' https://fonts.gstatic.com; \
  connect-src 'self' https://api.example.com; \
  object-src 'none'; \
  base-uri 'self'; \
  form-action 'self'; \
  frame-ancestors 'none'; \
  upgrade-insecure-requests

# Keuntungan pendekatan ini:
# βœ… Tidak perlu whitelist banyak domain
# βœ… Lebih aman β€” attacker tidak bisa bypass dengan domain baru
# βœ… strict-dynamic memudahkan integrasi dengan bundler
# βœ… object-src 'none' mencegah plugin berbahaya
# βœ… base-uri 'self' mencegah base tag injection
# βœ… form-action 'self' mencegah form hijacking

Langkah 3: Konfigurasi Framework Populer

Contoh: CSP untuk React, Vue, Angular
# ============================================
# REACT (Create React App / Next.js)
# ============================================

# Next.js β€” next.config.js
const crypto = require('crypto');

module.exports = {
  async headers() {
    return [{
      source: '/(.*)',
      headers: [
        {
          key: 'Content-Security-Policy',
          value: `
            default-src 'self';
            script-src 'self' 'nonce-NONCE_PLACEHOLDER' 'strict-dynamic';
            style-src 'self' 'unsafe-inline';
            img-src 'self' blob: data: https:;
            font-src 'self';
            connect-src 'self' https://api.example.com;
            object-src 'none';
            base-uri 'self';
          `.replace(/\n/g, '')
        }
      ]
    }];
  }
};

# ============================================
# VUE.JS (Nuxt.js)
# ============================================

# nuxt.config.js β€” menggunakan helmet middleware
export default {
  serverMiddleware: [
    helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          scriptSrc: ["'self'", "'nonce-{dynamic}'"],
          styleSrc: ["'self'", "'unsafe-inline'"],
          imgSrc: ["'self'", 'data:', 'https:'],
          connectSrc: ["'self'", 'https://api.example.com'],
          objectSrc: ["'none'"],
          frameAncestors: ["'none'"]
        }
      }
    })
  ]
}

# ============================================
# ANGULAR (Universal / Server-Side Rendering)
# ============================================

# Express middleware untuk Angular SSR
app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.cspNonce = nonce;
  
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", `'nonce-${nonce}'`, "'strict-dynamic'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'", 'data:', 'https:'],
        connectSrc: ["'self'"],
        objectSrc: ["'none'"]
      }
    }
  })(req, res, next);
});

Langkah 4: Migrasi Bertahap

⚠️ Strategi Migrasi Bertahap
  1. Phase 1 β€” Monitor Only: Deploy dengan Content-Security-Policy-Report-Only dan report endpoint. Kumpulkan data selama 1-2 minggu.
  2. Phase 2 β€” Whitelist Legitimate: Analisis reports, tambahkan resource legitimate ke policy. Pastikan tidak ada false positives.
  3. Phase 3 β€” Enforce: Switch dari Report-Only ke Content-Security-Policy. Monitor violation reports untuk masalah yang terlewat.
  4. Phase 4 β€” Tighten: Secara bertahap ketatkan policy β€” hapus 'unsafe-inline', migrasi ke nonce/hash, kurangi whitelist domain.

7. Best Practices dan Kesalahan Umum

Best Practices

πŸ’‘ CSP Best Practices Checklist
  • βœ… Gunakan 'nonce' + 'strict-dynamic' daripada whitelist domain
  • βœ… Hindari 'unsafe-inline' untuk script-src (boleh untuk style-src)
  • βœ… Hindari 'unsafe-eval' β€” refactor kode yang menggunakan eval()
  • βœ… Selalu set object-src 'none' untuk mencegah plugin berbahaya
  • βœ… Selalu set base-uri 'self' untuk mencegah base tag injection
  • βœ… Gunakan frame-ancestors sebagai pengganti X-Frame-Options
  • βœ… Implementasikan reporting sebelum enforce
  • βœ… Generate nonce di server menggunakan CSPRNG (crypto.randomBytes)
  • βœ… Gunakan upgrade-insecure-requests untuk migrasi HTTPS
  • βœ… Test policy di staging sebelum deploy ke production

Kesalahan Umum dalam Implementasi CSP

Kesalahan Mengapa Berbahaya Solusi
Menggunakan 'unsafe-inline' di script-srcMelemahkan perlindungan XSS β€” inline script berbahaya tetap bisa dieksekusiGunakan nonce atau hash
Whitelist terlalu banyak domainMeningkatkan attack surface β€” attacker bisa memanfaatkan XSS di domain yang di-whitelistGunakan 'strict-dynamic'
Wildcard (*) di script-srcMengizinkan script dari mana saja β€” CSP menjadi tidak bergunaTentukan sumber spesifik
Nonce yang bisa diprediksiAttacker bisa menebak nonce dan menyuntikkan script berbahayaGunakan CSPRNG (crypto.randomBytes)
Tanpa object-src 'none'Plugin seperti Flash bisa dimanfaatkan untuk bypass CSPSelalu set object-src 'none'
Menggunakan CDN yang bisa di-uploadJika attacker bisa upload ke CDN yang di-whitelist, mereka bisa bypass CSPGunakan SRI (Subresource Integrity)
Tidak menguji di stagingPolicy yang terlalu ketat bisa memecah fungsionalitas websiteSelalu gunakan Report-Only dulu

Tools untuk Testing CSP

Tool Fungsi URL
CSP EvaluatorMengevaluasi kekuatan policy CSPcsp-evaluator.withgoogle.com
Report URILayanan hosted untuk menerima dan menganalisis CSP reportsreport-uri.com
Security HeadersScan header keamanan websitesecurityheaders.com
observatory.mozilla.orgAnalisis keamanan header oleh Mozillaobservatory.mozilla.org
CSP Scanner (Burp Suite)Scanner CSP di Burp SuiteBurp Suite Extensions

8. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Content Security Policy:

Pertanyaan 1: Apa tujuan utama Content Security Policy (CSP)?

a) Mencegah serangan XSS dan injection dengan membatasi sumber konten
b) Mengenkripsi semua data di browser
c) Mempercepat loading website
d) Menggantikan HTTPS

Pertanyaan 2: Apa perbedaan antara 'nonce' dan 'hash' dalam CSP?

a) Nonce dan hash adalah hal yang sama
b) Nonce adalah string acak per-request; hash dihitung dari konten script
c) Nonce digunakan untuk gambar; hash untuk script
d) Hash lebih aman dari nonce dalam semua kasus

Pertanyaan 3: Apa fungsi dari 'strict-dynamic' dalam CSP?

a) Mengizinkan semua script eksekusi secara dinamis
b) Mempercayai script yang dimuat oleh script yang sudah trusted
c) Menggantikan semua direktif CSP lainnya
d) Mencegah script di-cache oleh browser

Pertanyaan 4: Header mana yang sebaiknya digunakan untuk testing CSP sebelum enforce?

a) Content-Security-Policy
b) Content-Security-Policy-Report-Only
c) X-Content-Security-Policy
d) CSP-Testing-Mode

Pertanyaan 5: Mengapa menggunakan 'unsafe-inline' di script-src berbahaya?

a) Karena memperlambat website
b) Karena mengizinkan inline script berbahaya dieksekusi, melemahkan perlindungan XSS
c) Karena tidak didukung oleh browser modern
d) Karena membuat CSP header terlalu panjang
πŸ” Zoom
100%
🎨 Tema