Header Keamanan Dijelaskan: CSP, HSTS, dan Selebihnya
Panduan praktis CSP dengan nonce dan strict-dynamic, HSTS dan preload, frame-ancestors, Referrer-Policy, Permissions-Policy, dan COOP, plus konfigurasi Next.js.
· 7 menit baca · Lina Source LLC
Header keamanan adalah instruksi yang diberikan server Anda kepada browser: hanya jalankan skrip ini, hanya bicara dengan saya lewat HTTPS, jangan biarkan situs lain memasang halaman ini di dalam frame. Header itu tidak memperbaiki bug di kode Anda, tetapi membatasi apa yang bisa dilakukan penyerang dengan sebuah bug. Lubang cross-site scripting di balik Content Security Policy yang ketat adalah masalah yang jauh lebih kecil daripada lubang yang sama tanpa CSP.
Sebagian besar header ini hanya satu baris konfigurasi. Pengecualiannya adalah CSP, yang butuh perencanaan. Panduan ini membahas apa yang dilakukan setiap header, nilai yang masuk akal untuk aplikasi web pada umumnya, dan cara menerapkannya tanpa merusak produksi.
Content-Security-Policy
CSP memberi tahu browser sumber skrip, gaya, gambar, frame, dan koneksi mana yang diizinkan. Tugas utamanya adalah menghentikan skrip yang disuntikkan agar tidak berjalan. Allowlist berisi domain terdengar seperti pendekatan yang paling jelas, tetapi ia punya sejarah panjang soal bypass: CDN mana pun yang diizinkan dan menampung konten pengguna atau versi pustaka lama bisa dipakai memuat skrip yang dikendalikan penyerang.
Nonce dan strict-dynamic
Pendekatan yang bertahan adalah kebijakan berbasis nonce. Server membangkitkan nilai acak per respons, memasukkannya ke header CSP, dan menambahkannya sebagai atribut nonce pada setiap tag script yang dirender. Tag script yang disuntikkan tidak tahu nonce-nya sehingga diblokir. Menambahkan 'strict-dynamic' memungkinkan skrip yang dimuat oleh skrip tepercaya memuat skrip lanjutan, sehingga bundler dan tag manager tetap berfungsi tanpa perlu mendaftar setiap domain.
Content-Security-Policy:
default-src 'self';
script-src 'nonce-4AEemGb0xJptoIGFP3Nd' 'strict-dynamic';
style-src 'self' 'nonce-4AEemGb0xJptoIGFP3Nd';
img-src 'self' data: https:;
connect-src 'self';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self';
upgrade-insecure-requestsHeader ini dikirim dalam satu baris; di sini ia dipecah agar mudah dibaca. Beberapa direktif dalam kebijakan itu melakukan lebih banyak daripada yang tampak. object-src 'none' memblokir konten plugin lawas. base-uri 'none' mencegah tag base yang disuntikkan mengalihkan URL skrip relatif. form-action 'self' mencegah form yang disuntikkan mengirim kredensial ke tempat lain. Nonce harus tidak bisa ditebak dan baru pada setiap respons, yang berarti halaman yang memakainya tidak bisa disajikan dari cache statis.
Terapkan bertahap dengan Report-Only
CSP ketat yang diterapkan secara membabi buta pasti merusak sesuatu: potongan analytics, event handler inline, widget pihak ketiga. Kirim kebijakannya dulu sebagai Content-Security-Policy-Report-Only. Browser tidak menerapkan apa pun tetapi melaporkan setiap pelanggaran ke endpoint yang disebut di direktif report-to atau report-uri. Kumpulkan laporan untuk sementara waktu, perbaiki atau izinkan yang memang sah, lalu ganti nama header-nya menjadi mode penegakan. Biarkan pelaporan tetap menyala setelah penegakan, karena pelanggaran baru berarti entah regresi atau serangan. Harapkan ada derau di laporan yang berasal dari ekstensi browser, yang menyuntikkan skripnya sendiri ke halaman; saring semua itu berdasarkan sumbernya sebelum memutuskan apa yang akan diizinkan.
Strict-Transport-Security
HSTS memberi tahu browser untuk memakai HTTPS bagi domain Anda selama periode tertentu, bahkan jika pengguna mengetik http:// atau mengeklik tautan lama. Itu menutup celah di mana penyerang jaringan bisa mencegat permintaan HTTP polos yang pertama dan membuang redirect ke HTTPS. Browser hanya menghormati header ini ketika ia tiba lewat HTTPS, dan browser juga menolak membiarkan pengguna menerobos error sertifikat pada domain ber-HSTS, yang memang itulah tujuannya tetapi sekaligus risikonya jika sertifikat kedaluwarsa.
Mulailah dengan max-age yang singkat, misalnya sehari, pastikan tidak ada yang rusak, lalu naikkan menjadi satu atau dua tahun. includeSubDomains memperluas aturannya ke setiap subdomain, jadi pastikan lebih dulu semuanya menyajikan HTTPS yang valid, termasuk situs marketing lama dan perkakas internal di domain yang sama.
Direktif preload, digabung dengan pengajuan ke daftar preload browser, menanamkan domain Anda ke dalam browser sehingga kunjungan pertama sekalipun memakai HTTPS. Ia menuntut max-age minimal satu tahun dan includeSubDomains. Perlakukan sebagai pintu satu arah: penghapusan dari daftar itu mungkin dilakukan tetapi butuh waktu lama untuk sampai ke pengguna, dan subdomain mana pun yang tidak bisa HTTPS menjadi tak terjangkau selama masa itu.
Header satu baris
- X-Content-Type-Options: nosniff. Menghentikan browser menebak tipe konten yang berbeda dari yang Anda deklarasikan, yang mencegah file unggahan yang disajikan sebagai teks dieksekusi sebagai skrip.
- frame-ancestors (di dalam CSP) dan X-Frame-Options: DENY. Mengontrol siapa yang boleh menyematkan halaman Anda di dalam frame, yang merupakan pertahanan terhadap clickjacking. Browser modern memakai frame-ancestors dan mengabaikan X-Frame-Options ketika keduanya diset; pertahankan X-Frame-Options untuk klien lama. Gunakan 'self' atau SAMEORIGIN jika Anda mem-frame halaman Anda sendiri.
- Referrer-Policy: strict-origin-when-cross-origin. Hanya mengirim origin Anda, bukan path dan query string lengkap, ke situs lain. Ini mencegah token dan ID di dalam URL bocor ke pihak ketiga. Gunakan no-referrer untuk halaman yang sangat sensitif.
- Permissions-Policy. Mematikan fitur browser yang tidak Anda pakai, seperti camera=(), microphone=(), geolocation=(), dan payment=(), sehingga kode yang disuntikkan atau disematkan tidak bisa memintanya.
- Cross-Origin-Opener-Policy: same-origin. Menempatkan halaman Anda di grup konteks penjelajahan sendiri, sehingga jendela yang dibuka situs lain tidak bisa mempertahankan referensi ke sana. Gunakan same-origin-allow-popups jika Anda mengandalkan popup OAuth atau pembayaran.
- Cross-Origin-Resource-Policy: same-origin atau same-site. Memberi tahu browser agar tidak membiarkan origin lain memuat respons Anda sebagai gambar, skrip, atau subresource lain. Gunakan same-site jika Anda menyajikan aset dari subdomain saudara, dan cross-origin untuk aset yang memang dimaksudkan disematkan di tempat lain.
Anda bisa membuang X-XSS-Protection. Filter yang dikendalikannya sudah dihapus dari browser modern, dan CSP adalah penggantinya. Jika sebuah scanner bersikeras, setel ke 0. Demikian pula, hindari header yang hanya menambah informasi bagi penyerang: X-Powered-By dan banner Server yang terperinci mengungkap framework dan versi Anda secara cuma-cuma. Di Next.js, setel poweredByHeader: false di konfigurasi untuk menghilangkan yang pertama.
Sebuah konfigurasi Next.js
Header statis sebaiknya ditempatkan di next.config.ts, di mana fungsi headers() menerapkannya ke setiap route. Nilai di bawah ini adalah default yang masuk akal untuk aplikasi yang tidak menyematkan dirinya di dalam frame, tidak memakai kamera atau lokasi, dan menyajikan asetnya sendiri. Sesuaikan masing-masing dengan apa yang sebenarnya dilakukan aplikasi Anda, jangan menyalinnya mentah-mentah.
// next.config.ts
import type { NextConfig } from "next";
const securityHeaders = [
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains",
},
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=(), payment=()",
},
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
{ key: "Cross-Origin-Resource-Policy", value: "same-origin" },
];
const nextConfig: NextConfig = {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
},
};
export default nextConfig;CSP membutuhkan nonce baru per permintaan, jadi ia diset di middleware. Next.js membaca nonce dari header Content-Security-Policy pada permintaan dan menerapkannya ke skrip milik framework selama rendering. Anda bisa membacanya di server component dengan headers().get("x-nonce") untuk tag script Anda sendiri.
// middleware.ts (di Next.js 16 file-nya proxy.ts dan fungsinya proxy)
import { NextResponse, type NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
const csp = [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'nonce-${nonce}'`,
"img-src 'self' blob: data:",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"upgrade-insecure-requests",
].join("; ");
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-nonce", nonce);
requestHeaders.set("Content-Security-Policy", csp);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set("Content-Security-Policy", csp);
return response;
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};Halaman yang dirender dengan nonce harus dirender secara dinamis, karena halaman statis akan memakai ulang satu nonce untuk setiap pengunjung. Selama masa penerapan, ubah nama header di kedua tempat menjadi Content-Security-Policy-Report-Only dan tambahkan endpoint pelaporan. Di lingkungan pengembangan, React mungkin membutuhkan 'unsafe-eval' di script-src untuk fitur debugging-nya; tambahkan hanya ketika NODE_ENV bernilai development.
Cara memverifikasinya
- Jalankan curl -sI https://domain-anda.example/ terhadap halaman beranda, sebuah halaman dinamis, sebuah route API, dan sebuah aset statis, lalu baca header pada masing-masing. Header yang hanya diset pada halaman HTML adalah celah yang umum.
- Buka developer tools browser. Pelanggaran CSP muncul di konsol lengkap dengan direktif dan resource yang diblokir, dan tab Network menunjukkan header yang benar-benar disajikan.
- Periksa di balik CDN atau reverse proxy Anda sekaligus di origin. Proksi kadang membuang, menggandakan, atau menimpa header, dan dua header CSP yang bertentangan sama-sama ditegakkan, sehingga kebijakan efektifnya lebih ketat daripada keduanya.
- Gunakan pemeriksa publik seperti Mozilla HTTP Observatory untuk pendapat kedua yang cepat, dan CSP Evaluator milik Google untuk menemukan direktif CSP yang lemah.
- Tambahkan tes di CI yang meminta route penting dan memastikan header-nya ada, sehingga refaktor konfigurasi tidak bisa diam-diam menghilangkannya.
Header adalah konfigurasi, dan justru karena itu ia mudah melenceng: route handler baru yang menyusun responsnya sendiri, aturan proksi yang menimpa default, CSP yang dilonggarkan dengan 'unsafe-inline' demi meloloskan sebuah rilis. Review semuanya seperti Anda mereview kode. CodeAuditAgent membaca konfigurasi di repositori publik atau snippet yang ditempel bersama sisa kodenya, lalu melaporkan header yang hilang atau melemah lengkap dengan tingkat keparahan, konfigurasi yang dikutip, dan usulan perbaikan.
Urutan kerja yang masuk akal: header satu baris hari ini, HSTS dengan max-age singkat minggu ini, dan CSP berbasis nonce dalam mode Report-Only segera setelah Anda bisa mengumpulkan laporannya.