Bayangkan Anda sedang login ke dashboard website, lalu membuka tab lain untuk membaca artikel. Tanpa Anda sadari, halaman kedua mencoba mengirim permintaan ke website pertama—misalnya mengubah email akun, mengganti pengaturan, atau menghapus sebuah data. Karena browser masih membawa cookie login, server bisa mengira permintaan itu benar-benar datang dari Anda.
Inilah gambaran sederhana Cross-Site Request Forgery atau CSRF. Masalahnya bukan selalu pada password yang bocor. Sering kali, aplikasi terlalu percaya bahwa setiap permintaan dari browser pengguna yang sudah login pasti berasal dari tindakan pengguna tersebut.
Apa sebenarnya yang terjadi dalam serangan CSRF?
CSRF memanfaatkan kebiasaan browser yang otomatis menyertakan cookie ketika mengirim permintaan ke suatu website. Penyerang tidak harus mengetahui isi cookie atau mengambil alih akun. Ia cukup membuat halaman atau tautan yang memicu permintaan ke website target.
Contohnya, sebuah endpoint lama mungkin mengubah status pesanan hanya dengan URL seperti /order/123/approve. Jika endpoint tersebut dapat dipanggil dengan metode GET dan tidak meminta bukti tambahan, halaman lain dapat memancing browser korban untuk mengaksesnya.
Karena itu, penggunaan metode POST saja belum otomatis mencegah CSRF. Form tersembunyi atau JavaScript dari situs lain tetap dapat mencoba mengirim permintaan POST dalam kondisi tertentu. Perlindungan harus memeriksa apakah permintaan tersebut memang dibuat oleh halaman aplikasi yang sah.
Token CSRF: tanda tangan kecil untuk setiap tindakan penting
Pendekatan yang paling umum adalah synchronizer token. Server membuat token acak atau token yang terikat pada sesi pengguna, lalu menyisipkannya ke dalam form. Saat form dikirim, server memeriksa token tersebut. Situs lain mungkin bisa memicu permintaan, tetapi biasanya tidak dapat membaca token yang hanya tersedia di halaman aplikasi target.
Contoh sederhana di PHP:
<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
<form method="post" action="/profile/update.php">
<input type="hidden" name="csrf_token"
value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES, 'UTF-8') ?>">
<button type="submit">Simpan</button>
</form>Di sisi server, token harus dibandingkan dengan nilai yang tersimpan di sesi sebelum perubahan dilakukan:
$submitted = $_POST['csrf_token'] ?? '';
$expected = $_SESSION['csrf_token'] ?? '';
if (!$expected || !hash_equals($expected, $submitted)) {
http_response_code(403);
exit('Permintaan tidak valid');
}Fungsi random_bytes() dirancang untuk menghasilkan byte acak yang aman secara kriptografis. Sementara itu, hash_equals() membantu membandingkan nilai rahasia tanpa membocorkan informasi melalui perbedaan waktu eksekusi. Keduanya tersedia dalam PHP modern dan didokumentasikan di manual resmi PHP.
Jangan jadikan token sebagai pengganti otorisasi
Token CSRF hanya menjawab pertanyaan: “Apakah permintaan ini kemungkinan dibuat dari halaman aplikasi yang sah?” Token tidak menjawab: “Apakah pengguna ini berhak menghapus data tersebut?”
Setiap endpoint tetap perlu memeriksa autentikasi dan otorisasi. Misalnya, pengguna yang boleh mengedit profil belum tentu boleh menghapus akun lain. Urutannya sebaiknya jelas:
- Pastikan pengguna sudah login.
- Periksa token CSRF.
- Validasi input yang dikirim.
- Periksa hak akses terhadap objek atau tindakan tersebut.
- Baru lakukan perubahan data.
Dokumentasi WordPress juga menegaskan bahwa nonce bukan mekanisme autentikasi atau kontrol akses. Nonce membantu mengurangi risiko CSRF, tetapi tetap harus digunakan bersama pemeriksaan kemampuan pengguna seperti current_user_can().
Bagaimana penerapannya di WordPress?
Jika Anda membuat plugin atau halaman admin WordPress, gunakan API nonce bawaan daripada membuat sistem token sendiri tanpa alasan kuat. Untuk form, WordPress menyediakan wp_nonce_field() saat membuat token dan check_admin_referer() atau wp_verify_nonce() saat memeriksanya.
// Saat menampilkan form
wp_nonce_field('update_profile', 'profile_nonce');
// Saat memproses form
if (
!isset($_POST['profile_nonce']) ||
!wp_verify_nonce(
sanitize_text_field(wp_unslash($_POST['profile_nonce'])),
'update_profile'
)
) {
wp_die('Permintaan tidak valid.');
}
if (!current_user_can('edit_users')) {
wp_die('Anda tidak memiliki izin.');
}Untuk REST API WordPress yang menggunakan autentikasi berbasis cookie, nonce dengan action wp_rest digunakan untuk membantu memvalidasi permintaan. Namun, endpoint tetap perlu memeriksa permission_callback agar hanya pengguna dengan hak yang sesuai yang dapat menjalankan tindakan tersebut.
SameSite membantu, tetapi bukan satu-satunya lapisan
Atribut cookie SameSite membuat browser lebih selektif dalam mengirim cookie pada permintaan lintas situs. Nilai Lax sering menjadi kompromi antara keamanan dan kompatibilitas, sedangkan Strict lebih ketat. Namun, SameSite sebaiknya dipandang sebagai defense in depth, bukan pengganti token CSRF untuk semua aplikasi.
Ada beberapa alasan. Website bisa memiliki subdomain yang tidak semuanya dipercaya. Selain itu, endpoint yang mengubah data melalui GET masih berisiko ketika cookie SameSite=Lax tetap dikirim pada navigasi tertentu. Prinsip yang lebih aman adalah menjadikan GET hanya untuk membaca data, sedangkan perubahan harus menggunakan POST, PUT, PATCH, atau DELETE dan tetap dilindungi.
Checklist pemeriksaan CSRF
- Pastikan semua tindakan yang mengubah data tidak dipicu melalui GET.
- Tambahkan token CSRF pada form dan permintaan AJAX yang membutuhkan autentikasi cookie.
- Jangan menaruh token CSRF di URL karena dapat masuk ke riwayat browser, log, atau header Referer.
- Gunakan HTTPS dan atur cookie sesi dengan
Secure,HttpOnly, sertaSameSitesesuai kebutuhan. - Periksa token sebelum validasi bisnis dan sebelum perubahan database.
- Jangan menganggap token CSRF sebagai pengganti pemeriksaan role atau capability.
- Uji endpoint sensitif dengan browser yang sedang login dan halaman dari origin berbeda.
Apa artinya bagi kita?
CSRF biasanya tidak terlihat seperti serangan dramatis. Tidak ada layar login palsu dan tidak selalu ada error yang jelas. Dampaknya baru terasa ketika pengaturan berubah, data terhapus, atau tindakan penting dilakukan atas nama pengguna.
Perbaikannya relatif sederhana: gunakan token yang dibuat dengan aman, validasi di server, batasi perubahan pada metode HTTP yang tepat, dan tetap periksa izin pengguna. Untuk WordPress, manfaatkan API keamanan bawaan. Untuk PHP, gunakan sumber keacakan dan perbandingan rahasia yang sesuai, lalu jadikan perlindungan CSRF sebagai bagian dari pola standar setiap kali membuat endpoint baru.
Rujukan teknis: OWASP CSRF Prevention Cheat Sheet, WordPress Nonces, dan PHP random_bytes().
Sumber & bacaan lebih lanjut
- OWASP Cross-Site Request Forgery Prevention Cheat Sheet
- WordPress Nonces – Common APIs Handbook
- WordPress REST API Authentication
- PHP random_bytes() Manual
- PHP hash_equals() Manual
– Rio Yotto @rioyotto
