Security Headers #

Ketika browser memuat halaman website, server web mengirimkan sekumpulan informasi tambahan di tingkat header respons HTTP. Security Headers (Header Keamanan) adalah instruksi khusus yang memberi tahu browser bagaimana ia harus memperlakukan konten halaman kita—apakah boleh dimuat di dalam bingkai halaman lain (iframe), protokol mana yang diizinkan untuk memuat skrip, jenis resource apa saja yang boleh dieksekusi, hingga pembatasan akses perangkat keras klien. Mengonfigurasi Security Headers dengan benar adalah langkah kritis yang sangat efektif untuk melindungi aplikasi web kita dari serangan umum seperti Cross-Site Scripting (XSS), Clickjacking, MIME Sniffing, dan pembocoran informasi teknologi (information disclosure). Kita akan membahas secara mendalam konfigurasi header esensial, penyusunan kebijakan keamanan konten (Content Security Policy / CSP), pengisian token acak dinamis (CSP Nonce), pembatasan fitur browser (Permissions Policy), serta metode pengujian kepatuhan standar keamanan industri.


Urgensi dan Cara Kerja Security Headers #

Secara logis, web server bertindak sebagai penyaji data, sedangkan browser bertindak sebagai penerjemah dan eksekutor data tersebut. Masalah keamanan muncul karena browser secara bawaan sangat mempercayai data apa pun yang dikirimkan oleh server. Jika server kita terkena serangan injeksi skrip jahat (XSS), browser pengunjung akan mengeksekusi skrip tersebut tanpa curiga.

Security Headers berfungsi sebagai kebijakan pembatasan hak akses bagi browser. Ketika browser menerima halaman web yang disertai Security Headers, browser akan mengaktifkan modul proteksi internalnya (seperti mematikan fitur tebakan jenis file, menolak merender iframe dari domain lain, atau membatasi eksekusi Javascript hanya dari domain tepercaya).

Caddy mempermudah penulisan header-header ini melalui direktif header bawaannya, yang dapat diterapkan secara global maupun per rute spesifik.


Konfigurasi Header Keamanan Esensial #

Berikut adalah contoh konfigurasi Caddyfile yang menyertakan sekumpulan Security Headers esensial untuk memproteksi lalu lintas web normal:

# Konfigurasi Security Headers esensial di Caddy
example.com {
    header {
        # 1. Proteksi Enkripsi Transportasi (HSTS)
        # Menginstruksikan browser selalu menggunakan HTTPS selama 2 tahun ke depan
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        
        # 2. Proteksi Sniffing Jenis Konten (MIME Sniffing)
        # Menolak instruksi browser menebak tipe berkas di luar deklarasi Content-Type
        X-Content-Type-Options "nosniff"
        
        # 3. Proteksi Pembajakan Klik (Clickjacking)
        # Halaman hanya boleh dimuat di dalam iframe oleh domain yang sama
        X-Frame-Options "SAMEORIGIN"
        
        # 4. Kebijakan Privasi Rujukan URL (Referrer Policy)
        # Hanya kirim data referer jika berpindah ke protokol HTTPS yang sama
        Referrer-Policy "strict-origin-when-cross-origin"
        
        # 5. Minimalkan Kebocoran Informasi Server
        # Hapus informasi versi web server Caddy dan bahasa backend dari respons header
        -Server
        -X-Powered-By
    }
    
    file_server {
        root /var/www/html
    }
}

Mari kita bedah secara mendalam fungsi masing-masing header di atas:

1. HTTP Strict Transport Security (HSTS) #

HSTS memberi tahu browser bahwa domain kita (dan seluruh subdomain di bawahnya) hanya boleh diakses melalui koneksi HTTPS yang aman. Jika pengguna mencoba mengetik http://example.com, browser akan secara otomatis mengubahnya menjadi https://example.com di sisi klien sebelum request dikirimkan ke jaringan, mencegah serangan SSL Stripping.

Aturan konfigurasinya meliputi:

  • max-age: Menentukan durasi ingatan browser dalam satuan detik. Nilai 63072000 setara dengan 2 tahun (direkomendasikan untuk produksi).
  • includeSubDomains: Menerapkan aturan yang sama untuk semua subdomain (misal app.example.com dan api.example.com).
  • preload: Mendaftarkan domain kita ke dalam daftar preload HSTS resmi yang ditanam langsung di dalam kode sumber browser (Chrome, Firefox, Safari). Begitu domain kita masuk daftar ini, browser tidak akan pernah mencoba membuka versi HTTP biasa bahkan pada kunjungan pertama kali.

[!WARNING] Aktifkan preload dengan sangat hati-hati! Setelah domain terdaftar di HSTS preload list browser, proses penghapusannya sangat rumit dan membutuhkan waktu berbulan-bulan. Jika di kemudian hari kita harus menjalankan server HTTP biasa pada salah satu subdomain karena alasan teknis, subdomain tersebut akan mati total (unreachable) bagi semua pengunjung. Mulailah pengujian HSTS tanpa preload dengan nilai max-age=300 (5 menit), kemudian naikkan ke 86400 (1 hari), hingga akhirnya kita siap berkomitmen ke durasi 2 tahun.

2. X-Content-Type-Options: nosniff #

Header ini mencegah browser melakukan teknik MIME Sniffing—yaitu menganalisis byte file yang diunduh untuk menebak tipe datanya secara mandiri. Sebagai contoh, jika server kita menyajikan file gambar (.png) namun penyerang berhasil mengunggah kode Javascript jahat di dalam byte gambar tersebut, browser tanpa header nosniff akan mengeksekusi gambar tersebut sebagai kode script. Menyetel header ini ke "nosniff" memaksa browser mematuhi deklarasi header Content-Type yang dikirim server secara ketat.

3. X-Frame-Options: SAMEORIGIN #

Header ini melindungi website dari serangan Clickjacking (pembajakan klik), di mana penyerang memuat halaman web kita di dalam bingkai transparan (iframe) di atas website jahat mereka untuk menipu pengguna agar mengklik tombol penting tanpa disadari.

  • DENY: Menolak halaman dimuat di dalam iframe oleh siapa pun.
  • SAMEORIGIN: Mengizinkan halaman dimuat di dalam iframe hanya jika domain pembungkusnya sama dengan domain website ini.

4. Referrer-Policy: strict-origin-when-cross-origin #

Mengontrol seberapa banyak informasi rujukan URL asal (Referer) yang dikirimkan oleh browser saat pengguna mengklik tautan keluar dari website kita. Pilihan "strict-origin-when-cross-origin" adalah titik keseimbangan terbaik antara privasi dan fungsionalitas: ia mengirimkan path URL penuh jika berada dalam domain yang sama, mengirimkan hanya domain utama jika berpindah ke domain lain, dan tidak mengirimkan informasi apa pun jika berpindah dari HTTPS ke HTTP biasa yang tidak terenkripsi.

5. Menyembunyikan Identitas Teknologi Stack #

Secara bawaan, Caddy (dan framework backend seperti PHP atau Express) menyertakan informasi identitas mereka pada respons header (seperti Server: Caddy atau X-Powered-By: PHP/8.2). Informasi ini memberikan petunjuk berharga bagi penyerang untuk mencari celah keamanan (exploit) yang spesifik pada versi teknologi tersebut. Kita dapat menghapusnya dari header respons dengan menyertakan tanda minus (-) sebelum nama header pada direktif header Caddy.


Content Security Policy (CSP) Tingkat Lanjut #

Content Security Policy (CSP) adalah pertahanan terkuat yang kita miliki untuk mendeteksi dan memitigasi serangan injeksi skrip (Cross-Site Scripting / XSS) dan injeksi data. CSP bertindak sebagai daftar izin (allowlist) bagi browser yang mendeklarasikan secara ketat dari domain mana saja aset digital (Javascript, CSS, Gambar, Font, Koneksi WebSocket, Iframe) boleh dimuat.

Berikut adalah konfigurasi CSP yang ketat namun fleksibel untuk aplikasi web modern:

# Konfigurasi CSP ketat di Caddyfile
example.com {
    header {
        Content-Security-Policy "
            default-src 'self';
            script-src 'self' https://cdn.jsdelivr.net;
            style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
            img-src 'self' data: https: blob:;
            font-src 'self' https://fonts.gstatic.com;
            connect-src 'self' https://api.example.com wss://ws.example.com;
            object-src 'none';
            frame-ancestors 'none';
            base-uri 'self';
            form-action 'self';
            upgrade-insecure-requests;
        "
    }
    
    reverse_proxy localhost:3000
}

Penjelasan Rinci Direktif CSP #

  • default-src 'self': Aturan jatuh-kembali (fallback). Semua jenis aset yang tidak ditentukan directive-nya secara spesifik di bawah hanya boleh dimuat dari domain yang sama ('self').
  • script-src 'self' ...: Membatasi asal eksekusi Javascript. Hanya izinkan script lokal dan script dari CDN tepercaya (cdn.jsdelivr.net).
  • style-src 'self' 'unsafe-inline' ...: Membatasi asal stylesheet CSS. Penggunaan 'unsafe-inline' terkadang dibutuhkan jika framework CSS kita (seperti Tailwind/Vuetify) menulis tag style secara dinamis di HTML.
  • img-src 'self' data: https: blob:: Mengizinkan pemuatan gambar dari domain lokal, skema Base64 (data:), protokol HTTPS luar, dan objek biner (blob:).
  • object-src 'none': Melarang total pemuatan plugin browser lawas yang rentan celah keamanan seperti Flash, Java Applet, atau Active-X (<object>, <embed>).
  • frame-ancestors 'none': Melarang halaman ini dimuat di dalam iframe oleh domain lain mana pun. Direktif ini adalah versi modern dari X-Frame-Options yang didukung penuh oleh browser modern.
  • upgrade-insecure-requests: Menginstruksikan browser untuk secara otomatis mengubah request semua aset HTTP (seperti gambar atau skrip) menjadi HTTPS sebelum memuatnya, mencegah masalah Mixed Content.

CSP Nonce Generation Dinamis #

Menerapkan CSP yang ketat sering kali terbentur kendala ketika aplikasi web kita membutuhkan penulisan inline script (skrip yang ditulis langsung di dalam dokumen HTML, seperti <script>console.log("hello")</script>). Secara default, CSP yang aman akan memblokir inline script tersebut karena penyerang sering kali menyisipkan XSS melalui metode ini.

Solusi terbaik untuk mengizinkan inline script yang sah secara aman tanpa membuka celah XSS adalah menggunakan CSP Nonce. Nonce adalah token kriptografi acak sekali pakai (number used once) yang dihasilkan oleh Caddy pada setiap request.

Caddy akan menyisipkan nilai nonce ini ke dalam header CSP, dan aplikasi backend kita harus menyisipkan nilai nonce yang sama pada tag <script> di HTML. Browser hanya akan mengeksekusi skrip jika nilai nonce pada tag HTML cocok dengan nilai nonce pada header respons HTTP:

# Konfigurasi CSP Nonce menggunakan UUID bawaan Caddy
example.com {
    header {
        # Kita memanfaatkan placeholder UUID unik request {http.request.uuid} sebagai nonce
        Content-Security-Policy "
            default-src 'self';
            script-src 'self' 'nonce-{http.request.uuid}' https://trusted-cdn.com;
            style-src 'self' 'unsafe-inline';
            object-src 'none';
        "
    }
    
    # Teruskan UUID nonce ini ke backend agar backend dapat merendernya di template HTML
    reverse_proxy localhost:8080 {
        header_up X-CSP-Nonce {http.request.uuid}
    }
}

Di sisi backend (misalnya menggunakan template Go HTML Template), kita menangkap header tersebut dan merendernya pada tag script:

<!-- index.html template backend -->
<!DOCTYPE html>
<html>
<head>
    <title>Aplikasi Aman</title>
</head>
<body>
    <h1>Selamat Datang</h1>
    
    <!-- Tag script inline aman karena memiliki nonce yang cocok -->
    <script nonce="{{.CspNonce}}">
        console.log("Inline script ini berhasil dieksekusi secara aman!");
    </script>
</body>
</html>

Melalui metode ini, jika penyerang mencoba menyisipkan tag <script> kustom, script tersebut akan ditolak mentah-mentah oleh browser karena penyerang tidak dapat menebak nilai token UUID acak yang dihasilkan Caddy untuk request tersebut.


CSP Report-Only untuk Fase Pengujian #

Mengaktifkan CSP yang ketat secara langsung di produksi sangat berisiko merusak fungsionalitas aplikasi web jika ada pustaka Javascript atau stylesheet penting yang lupa kita daftarkan di daftar izin.

Untuk mencegah kesalahan ini, kita harus menggunakan mode pengawasan terlebih dahulu menggunakan header Content-Security-Policy-Report-Only. Pada mode ini, browser tidak akan memblokir aset yang melanggar aturan, melainkan hanya akan mengirimkan laporan pelanggaran (violation reports) ke server pelaporan kita:

# Menguji CSP di produksi tanpa merusak fungsionalitas
example.com {
    header {
        # Mode Report-Only: Hanya mencatat, tidak memblokir
        Content-Security-Policy-Report-Only "
            default-src 'self';
            script-src 'self' https://cdn.jsdelivr.net;
            report-uri https://telemetri-log.example.com/csp-reports;
        "
    }
    
    reverse_proxy localhost:3000
}

Setelah kita memantau log laporan pelanggaran selama beberapa minggu dan yakin tidak ada false positive (tidak ada skrip sah yang terlewat), kita dapat mengubah nama header tersebut menjadi Content-Security-Policy untuk mengaktifkan pemblokiran secara aktif.


Permissions Policy (Pembatasan Fitur Browser) #

Permissions Policy (sebelumnya dikenal sebagai Feature Policy) memungkinkan kita untuk secara eksplisit mengontrol fitur dan API browser mana saja yang boleh digunakan oleh halaman web kita dan iframe di bawahnya.

Sebagai contoh, jika aplikasi web kita hanyalah sebuah blog sederhana, tidak ada alasan bagi sistem untuk mengakses kamera, mikrofon, atau lokasi GPS pengguna. Menonaktifkan fitur-fitur ini mencegah eksploitasi jika penyerang berhasil menyusupkan iklan berbahaya (malvertising) yang mencoba mengakses perangkat pengguna:

# Membatasi akses API browser klien
example.com {
    header {
        # Kosongkan tanda kurung () untuk menolak total akses fitur
        Permissions-Policy "
            camera=(),
            microphone=(),
            geolocation=(),
            payment=(),
            usb=(),
            accelerometer=(),
            gyroscope=(),
            fullscreen=(self) # Hanya izinkan mode fullscreen dari domain kita sendiri
        "
    }
    
    file_server
}

Penerapan Dry Snippet untuk Efisiensi Konfigurasi #

Jika kita mengelola banyak sub-domain atau beberapa virtual host di dalam satu berkas Caddyfile, menulis ulang blok header yang sama secara berulang akan menyebabkan Caddyfile kita menjadi panjang dan sulit dipelihara.

Kita dapat memanfaatkan fitur Snippet (blok kode yang dapat digunakan kembali) untuk membungkus seluruh Security Headers kita, lalu mengimpornya ke setiap blok situs secara instan:

# ─── DEFINISI SNIPPET SECURITY HEADERS ───
(keamanan_global) {
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        
        # Sembunyikan tanda tangan server
        -Server
        -X-Powered-By
        -X-AspNet-Version
    }
}

# ─── APLIKASI SNIPPET PADA BLOK SITUS ───

# 1. Mengamankan situs utama
example.com {
    import keamanan_global
    
    root * /var/www/html
    file_server
}

# 2. Mengamankan sub-domain API
api.example.com {
    import keamanan_global
    
    reverse_proxy localhost:8080
}

Mengonfigurasi Header di Tingkat Site vs Reverse Proxy #

Dalam menyusun Caddyfile, kita harus memahami perbedaan cakupan (scope) peletakan direktif header:

  1. Tingkat Site (Global di Blok Situs): Header diletakkan langsung di dalam blok domain. Aturan ini berlaku untuk seluruh respons yang keluar dari Caddy, termasuk halaman kesalahan internal yang dihasilkan Caddy sendiri (seperti halaman error 502 Bad Gateway saat backend mati).
  2. Tingkat Reverse Proxy (Lokal di reverse_proxy): Header ditulis menggunakan subdirektif header_down di dalam blok reverse proxy. Aturan ini hanya berlaku untuk respons yang sukses diteruskan dari server backend.
# Perbandingan Site Level vs Proxy Level Headers
example.com {
    # Site Level: Menjamin halaman error Caddy pun memiliki header keamanan ini
    header X-Frame-Options "SAMEORIGIN"
    
    reverse_proxy localhost:3000 {
        # Proxy Level: Manipulasi spesifik untuk respons dari backend
        # Hapus header X-Powered-By yang bocor dari server NodeJS/Express
        header_down -X-Powered-By
        
        # Tambahkan header kustom untuk debugging internal
        header_down X-Backend-Server "node-worker-01"
    }
}

Metode Pengujian dan Verifikasi Keamanan Header #

Setelah kita menerapkan seluruh Security Headers di Caddyfile, kita harus memvalidasi hasilnya untuk memastikan browser menerima parameter tersebut dengan benar.

1. Pengujian Baris Perintah (CLI Verification) #

Kita dapat menggunakan utilitas curl dengan parameter -I (hanya meminta header respons HTTP) dan memfilternya menggunakan grep untuk memverifikasi kehadiran header keamanan:

# Meminta respons header dari website produksi kita
curl -I -s https://example.com | grep -E -i "strict-transport|x-frame|x-content|referrer|content-security|permissions-policy|server"

# Output yang diharapkan:
# strict-transport-security: max-age=63072000; includeSubDomains; preload
# x-content-type-options: nosniff
# x-frame-options: SAMEORIGIN
# referrer-policy: strict-origin-when-cross-origin
# permissions-policy: camera=(), microphone=(), geolocation=()
# (Perhatikan: Tidak boleh ada output 'server: Caddy' karena sudah kita hapus)

2. Pengujian Menggunakan Alat Analisis Online #

Untuk mendapatkan penilaian komprehensif yang diakui industri, kita sangat disarankan menguji website kita melalui layanan audit online gratis berikut:

  • SecurityHeaders.com: Layanan ini memindai respons header website kita dan memberikan peringkat huruf (grade) dari F (sangat buruk) hingga A+ (keamanan tingkat tinggi). Menerapkan konfigurasi snippet keamanan_global di atas akan membantu website kita mendapatkan skor A atau A+ secara instan.
  • Observatory.mozilla.org: Alat analisis keamanan mendalam yang dikembangkan oleh Mozilla untuk memvalidasi kebijakan CSP, HSTS, dan ketahanan konfigurasi enkripsi kita.
  • SSLLabs.com: Menilai kualitas konfigurasi SSL/TLS. Secara bawaan, Caddy menggunakan parameter TLS modern (TLS 1.2 dan 1.3) yang aman sehingga akan langsung mendapatkan nilai A+ tanpa perlu modifikasi tambahan.

Ringkasan #

  • Fungsi Utama: Security Headers adalah instruksi HTTP yang memberi tahu browser bagaimana mengamankan konten halaman dari serangan sisi klien (XSS, Clickjacking, MIME Sniffing).
  • Proteksi HSTS: Gunakan Strict-Transport-Security untuk memaksa HTTPS. Hati-hati dengan parameter preload karena permanen dan sulit dihapus dari browser.
  • Mencegah Clickjacking: Terapkan X-Frame-Options: SAMEORIGIN atau direktif CSP frame-ancestors 'none' untuk melarang pemuatan halaman web kita di dalam iframe yang tidak sah.
  • Kebijakan CSP Ketat: Gunakan Content-Security-Policy untuk membatasi asal pemuatan aset skrip. Manfaatkan Content-Security-Policy-Report-Only untuk fase pengujian aman.
  • Keunggulan CSP Nonce: Cegah celah XSS pada inline script dengan menyisipkan nilai UUID dinamis {http.request.uuid} sebagai token pembuktian sah.
  • Permissions Policy: Minimalkan celah penyalahgunaan dengan menonaktifkan API sensor browser yang tidak digunakan (seperti kamera, mikrofon, GPS) menggunakan parameter kosong ().
  • Snippet Reusable: Manfaatkan snippet Caddyfile (security_headers) untuk menerapkan kebijakan keamanan secara konsisten di seluruh sub-domain tanpa duplikasi kode.
  • Pembersihan Tanda Tangan: Selalu hapus header informatif teknologi stack (-Server dan -X-Powered-By) agar penyerang kesulitan memetakan celah keamanan spesifik.

← Sebelumnya: Pembatasan IP   Berikutnya: CORS →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact