Bayangkan skenario Kamu sedang login ke aplikasi internet banking milik kamu di domain bank-anda.com. Di tab terpisah pada browser yang sama, kamu membuka sebuah situs forum atau blog hiburan yang ternyata dikelola oleh peretas (situs-jahat.com). Tanpa kamu sadari, begitu halaman situs-jahat.com terbuka, uang senilai puluhan juta rupiah dari rekening kamu tiba-tiba berpindah ke rekening peretas. Kamu tidak pernah mengklik tombol transfer, tidak pernah memasukkan PIN ulang, dan tidak melihat adanya keanehan pada layar tab bank kamu.
Bagaimana hal ini bisa terjadi? Jawabannya terletak pada salah satu kerentanan web paling klasik namun tetap mematikan hingga saat ini: Cross-Site Request Forgery (CSRF).
Dalam artikel ini, kita akan membongkar secara mendalam apa itu CSRF, mengapa browser mengizinkan serangan ini terjadi, perbedaan kritis CSRF dengan CORS dan XSS, serta panduan arsitektur pencegahan praktis untuk developer PHP (Laravel) dan JavaScript (Node.js/Express).
1. Pendahuluan & Pengertian CSRF
Apa Itu CSRF?
Cross-Site Request Forgery (CSRF atau XSRF) adalah jenis serangan keamanan web yang mengeksploitasi kepercayaan (trust) yang dimiliki oleh aplikasi web terhadap browser pengguna yang sedang terautentikasi.
Dalam serangan CSRF, penyerang tidak perlu mencuri kata sandi atau kredensial pengguna secara langsung. Sebaliknya, penyerang "menjebak" browser korban untuk mengirimkan HTTP Request yang melakukan aksi berbahaya (seperti mengubah alamat email, mengganti password, atau melakukan transaksi keuangan) ke aplikasi target di mana korban saat ini memiliki sesi aktif (logged in).
Karena alasan ini, CSRF sering disebut juga sebagai Session Riding atau One-Click Attack.
Mengapa Peretas Mengeksploitasi Kepercayaan Browser Terhadap Cookie?
Akar masalah utama dari kerentanan CSRF terletak pada perilaku default (default behavior) dari browser web HTTP.
Secara standar arsitektur web:
- Ketika pengguna berhasil login ke
bank-anda.com, server akan merespons dengan headerSet-Cookie: session_id=abc123xyz. - Untuk setiap HTTP Request berikutnya yang ditujukan ke
bank-anda.com, browser secara otomatis menyertakan cookiesession_idtersebut di dalam header request (Cookie: session_id=abc123xyz). - Browser melakukan hal ini secara implisit tanpa peduli situs mana yang menginisiasi request tersebut.
Jika kamu berada di situs-jahat.com dan situs tersebut memicu request ke bank-anda.com/api/transfer, browser akan tetap menempelkan cookie session milik bank-anda.com. Dari sudut pandang server bank-anda.com, request yang masuk membawa cookie session yang valid, sehingga server menganggap request tersebut sah berasal dari keinginan pengguna.
2. Mekanisme Serangan CSRF (Langkah demi Langkah)
Untuk memahami bagaimana CSRF berjalan di dunia nyata, mari kita bedah alur eksploitasinya step-by-step.
Skenario Eksploitasi 1: Form Submit Otomatis (POST Request)
Misalkan aplikasi bank-anda.com memiliki fitur transfer uang yang diproses melalui rute POST https://bank-anda.com/api/transfer dengan bodi parameter:
destination_accountamount
Peretas membuat situs situs-jahat.com yang berisi kode HTML dan JavaScript berikut:
<!-- Halaman di situs-jahat.com -->
<!DOCTYPE html>
<html>
<head>
<title>Selamat Kamu Mendapatkan Hadiah!</title>
</head>
<body>
<h1>Tunggu sejenak, sedang memproses hadiah...</h1>
<!-- Form tersembunyi yang mengarah ke server bank -->
<form id="csrfForm" action="https://bank-anda.com/api/transfer" method="POST">
<input type="hidden" name="destination_account" value="999000-REK-PERETAS" />
<input type="hidden" name="amount" value="10000000" />
</form>
<script>
// Otomatis submit form begitu halaman selesai dimuat
document.addEventListener("DOMContentLoaded", function() {
document.getElementById('csrfForm').submit();
});
</script>
</body>
</html>
Ketika korban yang sedang memiliki sesi aktif di bank-anda.com membuka halaman tersebut:
- Browser mengunduh dokumen HTML dari
situs-jahat.com. - JavaScript mengeksekusi
submit()pada form. - Browser mengirimkan permintaan
POST https://bank-anda.com/api/transfer. - Browser secara otomatis menyertakan header
Cookie: session_id=xyz...milikbank-anda.com. - Server
bank-anda.commenerima request, membaca cookie session, memverifikasi bahwa korban memang sudah login, dan mengeksekusi pemindahan dana sebesar Rp 10.000.000 ke akun peretas.
Skenario Eksploitasi 2: Tag Gambar / Resource Embedding (GET Request ber-Side-Effect)
Jika aplikasi web yang buruk mengizinkan perubahan data (state-changing) melalui HTTP GET Request (misalnya: https://bank-anda.com/email/update?email=baru@gmail.com), peretas bahkan tidak memerlukan JavaScript atau form!
Peretas cukup menyisipkan tag gambar di postingan forum atau email HTML:
<img src="https://bank-anda.com/email/update?email=peretas@hacker.com" width="0" height="0" />
Saat browser korban mencoba memuat gambar dari URL tersebut, browser mengirimkan request GET beserta cookie session. Server bank langsung memproses perubahan email akun korban tanpa ada konfirmasi tambahan.
3. Perbedaan Kritis: CSRF vs XSS vs CORS
Banyak developer web pemula sering kali tertukar antara CSRF, XSS, dan CORS policy. Menggabungkan ketiga konsep ini tanpa pemahaman yang benar dapat menimbulkan celah keamanan yang serius.
Perbandingan Ringkas
| Fitur / Parameter | CSRF (Cross-Site Request Forgery) | XSS (Cross-Site Scripting) | CORS (Cross-Origin Resource Sharing) |
|---|---|---|---|
| Fokus Kerentanan | Memaksa browser mengeksekusi tindakan/request ke domain lain. | Menyisipkan dan mengeksekusi skrip JavaScript liar di dalam DOM aplikasi target. | Mekanisme keamanan browser untuk membatasi akses pembacaan response cross-origin. |
| Arah Serangan | Dari situs luar (Cross-Origin) menembus server target. | Di dalam situs aplikasi target itu sendiri (Same-Origin). | Bukan serangan, melainkan fitur proteksi browser. |
| Apakah Attacker Bisa Membaca Respon? | TIDAK. Peretas hanya memicu eksekusi, tetapi tidak bisa melihat isi balasan server. | YA. Peretas bisa membaca isi DOM, cookie (non-HttpOnly), local storage, dan API response. | Browser memblokir pembacaan respon jika CORS tidak mengizinkan. |
| Penyebab Utama | Browser otomatis menyertakan cookie pada request cross-origin. | Kurangnya sanitasi & escaping pada input/output HTML. | Konfigurasi header HTTP Access-Control-Allow-Origin di server. |
Kesalahpahaman Umum: "CORS Sudah Cukup Mencegah CSRF"
Ini adalah mitos keamanan paling populer dan paling berbahaya di kalangan developer.
Fakta: CORS TIDAK dirancang untuk mencegah serangan CSRF.
Mengapa demikian?
CORS (Cross-Origin Resource Sharing) bertugas mengontrol apakah skrip JavaScript dari situs A diizinkan untuk membaca respon yang dikembalikan oleh situs B.
Namun pada serangan CSRF:
- Standard HTML Form Submission (
<form action="..." method="POST">) TIDAK memicu pemeriksaan CORS preflight (simple request). - Permintaan POST dari form tetap akan terkirim dan dieksekusi oleh server target.
- Walaupun browser kemudian menyembunyikan respon dari peretas karena kebijakan CORS, kerusakan pada database server target sudah terlanjur terjadi (transaksi sudah diproses).
Oleh karena itu, memasang konfigurasi CORS yang ketat sekalipun tidak akan melindungi aplikasi kamu dari eksploitasi CSRF berbasis form HTML.
4. Strategi & Mekanisme Defensif Utama
Untuk mengamankan aplikasi dari CSRF, industri keamanan web mengembangkan beberapa standar arsitektur pertahanan.
4.1 Synchronizer Token Pattern (Anti-CSRF Token)
Ini adalah teknik perlindungan CSRF paling standar dan teruji, yang digunakan oleh mayoritas framework web monolithic (Laravel, Django, Ruby on Rails).
Cara Kerja:
- Saat pengguna membuat sesi di server, server membuat sebuah string acak yang aman secara kriptografi (cryptographically secure random token), misalnya
CSRF-TOKEN-a1b2c3d4.... - Token ini disimpan di sesi server side (atau disinkronkan dengan sesi pengguna).
- Setiap kali server merender form HTML yang memiliki metode mutasi (
POST,PUT,PATCH,DELETE), server menyisipkan token ini ke dalam field tersembunyi (hidden input field). - Ketika form dikirimkan, client wajib mengirimkan nilai token tersebut kembali ke server.
- Middleware di server akan membandingkan token dari request dengan token yang ada di sesi server. Jika tidak cocok atau tidak ada, request langsung ditolak (
HTTP 419 Page ExpiredatauHTTP 403 Forbidden).
<!-- Contoh Form dengan Synchronizer Token Pattern -->
<form method="POST" action="/profile/update">
<!-- Hidden input berisi CSRF Token -->
<input type="hidden" name="_token" value="d9a8f7e6c5b4a321...">
<label>Email Baru:</label>
<input type="email" name="email">
<button type="submit">Simpan</button>
</form>
Mengapa Peretas Tidak Bisa Mencuri Token Ini?
Berdasarkan aturan Same-Origin Policy (SOP) browser, JavaScript dari situs-jahat.com TIDAK dapat membaca isi DOM dari halaman bank-anda.com. Karena peretas tidak bisa membaca isi dokumen HTML korban di bank-anda.com, peretas tidak bisa mengambil nilai _token tersembunyi untuk dimasukkan ke dalam payload serangan mereka.
4.2 Double-Submit Cookie Pattern
Teknik ini umumnya digunakan pada arsitektur aplikasi stateless atau Single Page Application (SPA) yang tidak menyimpan data sesi di memori server-side.
Cara Kerja:
- Server membuat token CSRF acak dan mengirimkannya ke client dalam bentuk Cookie (misal nama cookienya
XSRF-TOKEN). Cookie ini dapat dibaca oleh JavaScript client-side (HttpOnly: false). - Ketika aplikasi client (React, Vue, Angular, Axios) hendak melakukan request AJAX (
POST,PUT,DELETE), JavaScript membaca nilai dari cookieXSRF-TOKENtersebut. - JavaScript kemudian menduplikasi nilai token tersebut ke dalam Custom HTTP Request Header (misal:
X-XSRF-TOKENatauX-CSRF-TOKEN). - Server memverifikasi apakah isi dari Custom HTTP Header sama dengan isi dari Cookie yang ikut terkirim.
Mengapa Teknik Ini Aman?
Meskipun browser peretas secara otomatis menyertakan cookie XSRF-TOKEN saat mengirimkan request cross-site, skrip peretas di situs-jahat.com tidak bisa membaca isi cookie domain target untuk mengekstrak string token dan memasukkannya ke dalam header X-XSRF-TOKEN. Karena header kustom tersebut kosong atau salah, server akan menolak request tersebut.
4.3 Aturan Cookie SameSite (RFC 6265bis)
Atribut SameSite pada cookie adalah mekanisme pertahanan modern dari sisi browser yang memberikan kontrol penuh kepada developer atas kapan cookie boleh dikirim pada request cross-site.
Header Set-Cookie mendukung tiga nilai SameSite:
1. SameSite=Strict
- Cookie TIDAK PERNAH dikirim dalam permintaan cross-site apa pun.
- Bahkan jika pengguna mengklik link biasa dari email atau WhatsApp yang mengarah ke
bank-anda.com, cookie sesi tidak akan dikirim pada request pertama tersebut (pengguna akan terlihat seperti belum login sampai halaman direfresh/donavigasikan dari internal domain). - Penggunaan: Aplikasi dengan tingkat keamanan ultra tinggi (sistem perbankan, portal militer).
2. SameSite=Lax (Default pada Browser Modern)
- Cookie TIDAK dikirim pada permintaan cross-site yang berpotensi mengubah state (
POST,PUT,DELETE, AJAX cross-site). - Cookie TETAP dikirim pada navigasi tingkat atas (top-level navigation) dengan HTTP method yang aman/idempotent (seperti mengklik link biasa
<a href="...">dengan methodGET). - Penggunaan: Standar ideal untuk mayoritas aplikasi web modern (menyeimbangkan keamanan dan kenyamanan pengguna).
3. SameSite=None
- Cookie dikirim pada semua request cross-site (seperti perilaku cookie tradisional).
- Syarat Wajib: Wajib mengaktifkan atribut
Secure(Set-Cookie: session=123; SameSite=None; Secure). Ini berarti cookie hanya dikirim melalui koneksi terenkripsi HTTPS.
4.4 Autentikasi Berbasis Token (JWT dalam Header Authorization)
Jika aplikasi SPA/Mobile kamu menggunakan mekanisme autentikasi stateless dengan token JWT yang disimpan dalam memori (variabel JS) dan dikirim via header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Secara alami, aplikasi kamu kebal terhadap CSRF. Mengapa? Karena browser tidak pernah menyertakan header Authorization: Bearer ... secara otomatis pada request cross-site atau form submit. Header ini harus disuntikkan secara eksplisit via JavaScript.
Peringatan: Jika JWT kamu disimpan di dalam HTTP Cookie, maka risiko CSRF tetap ada, dan kamu wajib menerapkan atribut
SameSiteatau CSRF Token!
5. Panduan Implementasi Praktis
5.1 Proteksi CSRF di PHP / Laravel
Framework Laravel menyediakan perlindungan CSRF yang sangat matang secara out-of-the-box melalui middleware VerifyCsrfToken (pada Laravel 11+, dikelola via bootstrap/app.php).
A. Menggunakan Blade Directive
Saat kamu membuat form HTML di Blade, kamu cukup menambahkan directive @csrf:
<!-- resources/views/profile.blade.php -->
<form action="/user/update" method="POST">
@csrf
<input type="text" name="username" value="{{ auth()->user()->name }}">
<button type="submit">Update Profil</button>
</form>
Directive @csrf ini akan otomatis dirender menjadi:
<input type="hidden" name="_token" value="Qe1r2t3y4u5i6o7p8a9s0d...">
B. Setup HTTP AJAX (Axios / Fetch)
Laravel secara otomatis membuat cookie XSRF-TOKEN terenkripsi pada setiap respon. Library HTTP populer seperti Axios secara otomatis membaca cookie ini dan melampirkannya ke header X-XSRF-TOKEN pada setiap request mutasi data.
Jika kamu menggunakan JavaScript fetch manual, kamu perlu mengambil token dari tag <meta>:
<!-- Di bagian <head> HTML -->
<meta name="csrf-token" content="{{ csrf_token() }}">
// Di file JavaScript kamu
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/user/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': token
},
body: JSON.stringify({ username: 'Budi' })
});
C. Dikecualikan dari Proteksi CSRF (Exempting Routes)
Beberapa rute, seperti Webhook dari Payment Gateway (Midtrans, Stripe) atau callback eksternal, tidak dapat menyertakan CSRF token karena dikirim dari server pihak ketiga.
Pada Laravel 11+, kamu dapat mengecualikan rute tersebut melalui bootstrap/app.php:
// bootstrap/app.php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'stripe/*',
'api/midtrans/notification',
]);
})->create();
5.2 Proteksi CSRF di JavaScript / Node.js (Express.js)
Catatan Penting: Package legacy
csurfuntuk Express.js sudah didepresiasi (deprecated) dan sebaiknya tidak digunakan lagi untuk proyek baru.
Untuk aplikasi Node.js modern berbasis Express.js, pendekatan terbaik adalah menggabungkan konfigurasi cookie SameSite yang ketat dengan paket pengelola token aktif seperti csrf-csrf (Double Submit Cookie Pattern).
A. Mengkonfigurasi SameSite Cookie pada Express-Session
Langkah pertama dan termudah adalah memastikan sesi Express kamu dilindungi oleh atribut SameSite.
// server.js
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'kunci-rahasia-super-aman-12345',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // Mencegah akses cookie dari JavaScript (XSS Protection)
secure: process.env.NODE_ENV === 'production', // Wajib HTTPS di production
sameSite: 'lax' // Memblokir cookie pada cross-site POST form CSRF
}
}));
B. Menggunakan Library csrf-csrf untuk SPA / Modern Web
Jika kamu membutuhkan validasi CSRF Token berbasis Double-Submit Cookie di Express:
- Install dependency:
npm install csrf-csrf cookie-parser
- Konfigurasi middleware di Express:
const express = require('express');
const cookieParser = require('cookie-parser');
const { doubleCsrf } = require('csrf-csrf');
const app = express();
app.use(express.json());
app.use(cookieParser('cookie-secret-key'));
// System options untuk csrf-csrf
const {
doubleCsrfProtection, // Middleware validasi
generateToken // Function pembentuk token
} = doubleCsrf({
getSecret: () => 'SecretKunciSangatRahasia123',
cookieName: 'x-csrf-token',
cookieOptions: {
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
},
size: 64,
ignoredMethods: ['GET', 'HEAD', 'OPTIONS'],
});
// Endpoint untuk mendaptkan CSRF Token bagi Client
app.get('/api/csrf-token', (req, res) => {
const token = generateToken(req, res);
res.json({ csrfToken: token });
});
// Proteksi rute mutasi data dengan middleware doubleCsrfProtection
app.post('/api/transfer', doubleCsrfProtection, (req, res) => {
// Jika lolos dari middleware, berarti CSRF token valid
res.json({ status: 'success', message: 'Transfer berhasil diproses' });
});
app.listen(3000, () => console.log('Server berjalan pada port 3000'));
6. Best Practices & Common Pitfalls
Security Checklist & Best Practices
- Patuhi Prinsip Semantik HTTP Method:
- Jangan pernah mengizinkan aksi yang mengubah data (state-changing action) dilakukan melalui metode
GET. - Gunakan
GEThanya untuk operasi pembacaan yang aman (idempotent & safe).
- Jangan pernah mengizinkan aksi yang mengubah data (state-changing action) dilakukan melalui metode
- Terapkan Strategi Defense-in-Depth:
- Jangan hanya mengandalkan satu lapisan. Kombinasikan SameSite Cookie (
Lax/Strict) + Anti-CSRF Token + Cookie FlagHttpOnly&Secure.
- Jangan hanya mengandalkan satu lapisan. Kombinasikan SameSite Cookie (
- Validasi Origin & Referer Header:
- Untuk request mutasi data, buat middleware di server untuk memverifikasi apakah header
OriginatauReferercocok dengan domain utama aplikasi kamu.
- Untuk request mutasi data, buat middleware di server untuk memverifikasi apakah header
- Proteksi Aplikasi dari XSS:
- Proteksi CSRF terbaik sekalipun akan runtuh jika aplikasi kamu memiliki celah Cross-Site Scripting (XSS). Jika peretas bisa menginjeksikan skrip JS di domain kamu (XSS), mereka dapat dengan mudah mengambil CSRF token dan membaca data sensitif.
Pitfalls (Kesalahan Fatal yang Harus Dihindari)
- Mematikan CSRF Middleware Secara Global:
Banyak developer pemula mematikan middlewareVerifyCsrfTokendi Laravel hanya karena merasa kerepotan saat melakukan pengujian API via Postman. Ini adalah praktek yang sangat berbahaya jika aplikasi menggunakan autentikasi berbasis cookie session. - Menyimpan Access Token di
localStorageTanpa Memahami Risikonya:
Beberapa developer menghindari CSRF dengan menyimpan JWT dilocalStoragedaripada cookie. Meskipun ini membebaskan aplikasi dari CSRF, hal ini membuat aplikasi kamu sangat rentan terhadap XSS. Setiap skrip pihak ketiga atau dependensi npm yang terinfeksi dapat mencuri token darilocalStorage.
Penutup
Serangan Cross-Site Request Forgery (CSRF) memanfaatkan perilaku alami browser yang menyertakan cookie kredensial secara otomatis pada setiap request ke target host. Walaupun konsep serangan ini tampak sederhana, dampaknya bisa sangat fatal mulai dari pengambilalihan akun hingga kerugian finansial.
Mencegah CSRF memerlukan pemahaman yang jelas antara peran Anti-CSRF Token (Synchronizer / Double-Submit) dan penggunaan atribut cookie modern SameSite. Dengan menerapkan fitur keamanan bawaan framework seperti Laravel @csrf atau mengonfigurasi middleware yang tepat di Node.js, aplikasi web kamu akan terlindungi dari ancaman ini secara efektif.
Referensi terkait: OWASP CSRF Prevention Cheat Sheet, RFC 6265bis SameSite Specification, Laravel CSRF Protection, MDN SameSite Cookies.
Jika kamu ingin mendalami topik keamanan web terkait, baca artikel tentang XSS dan Cara Mencegahnya, SQL Injection dan Cara Mencegahnya, dan Cara Mengamankan REST API. Untuk pemahaman tentang otentikasi yang aman di Laravel, baca Password Hashing.
Fitur komentar belum diaktifkan oleh administrator.