Cross-Site Request Forgery¶
Pendahuluan¶
Serangan Pemalsuan Permintaan Lintas Situs (CSRF) terjadi ketika situs web, email, blog, pesan instan, atau program berbahaya mengelabui peramban web pengguna yang diautentikasi agar melakukan tindakan yang tidak diinginkan di situs tepercaya. Jika pengguna target diautentikasi ke situs tersebut, situs target yang tidak terlindungi tidak dapat membedakan antara permintaan sah yang sah dan permintaan palsu yang diautentikasi.
Karena permintaan peramban secara otomatis menyertakan semua kuki, termasuk kuki sesi, serangan ini berhasil kecuali otorisasi yang tepat digunakan. Artinya, mekanisme respons-tantangan situs target tidak memverifikasi identitas dan otoritas pemohon. Akibatnya, serangan CSRF membuat sistem target menjalankan fungsi yang ditentukan penyerang melalui peramban korban tanpa sepengetahuan korban (biasanya hingga setelah tindakan tidak sah dilakukan).
Namun, serangan CSRF yang berhasil hanya dapat mengeksploitasi kapabilitas yang diekspos oleh aplikasi yang rentan dan hak istimewa pengguna. Bergantung pada kredensial pengguna, penyerang dapat mentransfer dana, mengubah kata sandi, melakukan pembelian tidak sah, meningkatkan hak istimewa untuk akun target, atau melakukan tindakan apa pun yang diizinkan untuk dilakukan pengguna.
Singkatnya, prinsip-prinsip berikut harus diikuti untuk melindungi diri dari CSRF:
PENTING: Ingatlah bahwa Cross-Site Scripting (XSS) dapat mengalahkan semua teknik mitigasi CSRF! Meskipun kerentanan Cross-Site Scripting (XSS) dapat melewati perlindungan CSRF, token CSRF tetap penting untuk aplikasi web yang mengandalkan cookie untuk autentikasi. Pertimbangkan klien dan metode autentikasi untuk menentukan pendekatan terbaik untuk perlindungan CSRF di aplikasi Anda.
Lihat Lembar Panduan Pencegahan XSS OWASP untuk panduan terperinci tentang cara mencegah kelemahan XSS.
Pertama, periksa apakah kerangka kerja Anda memiliki perlindungan CSRF bawaan dan gunakanlah.
Jika kerangka kerja tidak memiliki perlindungan CSRF bawaan, tambahkan token CSRF ke semua permintaan yang mengubah status (permintaan yang menyebabkan tindakan di situs) dan validasi di backend.
Perangkat lunak stateful harus menggunakan pola token sinkronisasi.
Perangkat lunak stateless harus menggunakan kuki pengiriman ganda.
Jika situs berbasis API tidak dapat menggunakan tag
<form>, pertimbangkan untuk menggunakan header permintaan khusus.Terapkan setidaknya satu mitigasi dari bagian Mitigasi Pertahanan Berkelanjutan.
SameSite Cookie Attribute dapat digunakan untuk kuki sesi, tetapi berhati-hatilah untuk TIDAK menetapkan kuki khusus untuk suatu domain. Tindakan ini menimbulkan kerentanan keamanan karena semua subdomain dari domain tersebut akan berbagi kuki, dan ini khususnya menjadi masalah jika subdomain memiliki CNAME ke domain yang tidak berada dalam kendali Anda.
Pertimbangkan untuk menerapkan perlindungan berbasis interaksi pengguna untuk operasi yang sangat sensitif.
Pertimbangkan untuk memverifikasi asal dengan header standar.
Jangan gunakan permintaan GET untuk operasi perubahan status.
Jika karena alasan apa pun Anda melakukannya, lindungi sumber daya tersebut dari CSRF.
Mitigasi Berbasis Token¶
Pola token sinkronisasi adalah salah satu metode yang paling populer dan direkomendasikan untuk memitigasi CSRF.
Gunakan Implementasi CSRF Bawaan atau yang Sudah Ada untuk Perlindungan CSRF¶
Karena pertahanan token sinkronisasi sudah terpasang di banyak kerangka kerja, pastikan kerangka kerja Anda memiliki perlindungan CSRF yang tersedia secara bawaan sebelum Anda membangun sistem pembangkit token khusus. Misalnya, .NET dapat menggunakan perlindungan bawaan untuk menambahkan token ke sumber daya yang rentan terhadap CSRF. Jika Anda memilih untuk menggunakan perlindungan ini, .NET akan membuat Anda bertanggung jawab atas konfigurasi yang tepat (seperti manajemen kunci dan manajemen token).
Pola Token Sinkronisasi¶
Token CSRF harus dibuat di sisi server dan hanya boleh dibuat sekali per sesi pengguna atau setiap permintaan. Karena rentang waktu bagi penyerang untuk mengeksploitasi token yang dicuri minimal untuk token per permintaan, token CSRF lebih aman daripada token per sesi. Namun, penggunaan token per permintaan dapat menimbulkan masalah kegunaan.
Misalnya, kapabilitas peramban tombol "Kembali" dapat terhambat oleh token per permintaan karena halaman sebelumnya mungkin berisi token yang tidak lagi valid. Dalam hal ini, interaksi dengan halaman sebelumnya akan mengakibatkan peristiwa keamanan positif palsu CSRF di sisi server. Jika implementasi token per sesi terjadi setelah pembuatan token awal, nilainya disimpan dalam sesi dan digunakan untuk setiap permintaan berikutnya hingga sesi berakhir.
Ketika klien mengeluarkan permintaan, komponen sisi server harus memverifikasi keberadaan dan validitas token dalam permintaan tersebut dan membandingkannya dengan token yang ditemukan dalam sesi pengguna. Permintaan harus ditolak jika token tersebut tidak ditemukan dalam permintaan atau nilai yang diberikan tidak sesuai dengan nilai dalam sesi pengguna. Tindakan tambahan seperti mencatat peristiwa tersebut sebagai potensi serangan CSRF yang sedang berlangsung juga harus dipertimbangkan.
Token CSRF harus:
Unik per sesi pengguna.
Rahasia
Tidak dapat diprediksi (nilai acak besar yang dihasilkan oleh metode aman).
T oken CSRF mencegah CSRF karena tanpa token CSRF, penyerang tidak dapat membuat permintaan yang valid ke server backend.
Mentransmisikan Token CSRF dalam Pola Tersinkronisasi¶
Token CSRF dapat ditransmisikan ke klien sebagai bagian dari muatan respons, seperti respons HTML atau JSON, kemudian dapat ditransmisikan kembali ke server sebagai kolom tersembunyi pada pengiriman formulir atau melalui permintaan AJAX sebagai nilai header kustom atau bagian dari muatan JSON. Token CSRF tidak boleh ditransmisikan dalam cookie untuk pola tersinkronisasi. Token CSRF tidak boleh bocor di log server atau di URL. Permintaan GET berpotensi membocorkan token CSRF di beberapa lokasi, seperti riwayat peramban, berkas log, utilitas jaringan yang mencatat baris pertama permintaan HTTP, dan header Referer jika situs yang dilindungi tertaut ke situs eksternal.
Contoh:
<form action="/transfer.do" method="post">
<input type="hidden" name="CSRFToken" value="OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjJjZDE1ZDZMGYwMGEwOA==">
[...]
</form>
Karena permintaan dengan header khusus secara otomatis tunduk pada kebijakan asal yang sama, lebih aman untuk memasukkan token CSRF dalam header permintaan HTTP khusus melalui JavaScript daripada menambahkan token CSRF dalam parameter formulir bidang tersembunyi.
ALTERNATIF: Menggunakan Pola Kuki Double-Submit¶
Jika mempertahankan status token CSRF di server bermasalah, Anda dapat menggunakan teknik alternatif yang dikenal sebagai pola Kuki Double Submit. Teknik ini mudah diimplementasikan dan bersifat stateless. Ada berbagai cara untuk mengimplementasikan teknik ini, dengan pola naif sebagai variasi yang paling umum digunakan.
Kuki Double-Submit Bertanda Tangan (DISARANKAN)¶
Implementasi paling aman dari pola Kuki Double Submit adalah Kuki Double-Submit Bertanda Tangan, yang secara eksplisit mengikat token ke sesi terautentikasi pengguna (misalnya, ID sesi). Menandatangani token tanpa pengikatan sesi memberikan perlindungan minimal dan tetap rentan terhadap serangan injeksi kuki. Selalu ikat token CSRF secara eksplisit ke data spesifik sesi.
Jika token berisi informasi sensitif (seperti ID sesi atau klaim), selalu gunakan Hash-based Message Authentication (HMAC) dengan kunci rahasia sisi server. Ini mencegah pemalsuan token sekaligus memastikan integritas. HMAC lebih disukai daripada hashing sederhana dalam semua kasus karena melindungi dari berbagai serangan kriptografi. Untuk skenario yang memerlukan kerahasiaan isi token, gunakan enkripsi yang diautentikasi sebagai gantinya.
Menggunakan Token HMAC CSRF
Untuk menghasilkan token HMAC CSRF (dengan nilai pengguna yang bergantung pada sesi), sistem harus memiliki:
- Nilai yang bergantung pada sesi yang berubah setiap sesi login. Nilai ini hanya boleh valid untuk keseluruhan sesi pengguna yang diautentikasi. Hindari penggunaan nilai statis seperti email atau ID pengguna, karena tidak aman (1 | 2 | 3). Perlu dicatat bahwa memperbarui token CSRF terlalu sering, misalnya untuk setiap permintaan, adalah kesalahpahaman yang berasumsi bahwa hal itu menambah keamanan substansial namun justru merugikan pengalaman pengguna (1). Misalnya, Anda dapat memilih satu, atau kombinasi, dari nilai yang bergantung pada sesi berikut:
ID sesi sisi server (misalnya PHP atau ASP.NET). Nilai ini tidak boleh meninggalkan server atau berupa teks biasa di Token CSRF.
Nilai acak (misalnya UUID) dalam JWT yang berubah setiap kali JWT dibuat.
Kunci kriptografi rahasia. Jangan disamakan dengan nilai acak dari implementasi naif. Nilai ini digunakan untuk menghasilkan hash HMAC. Idealnya, simpan kunci ini seperti yang dibahas di halaman Penyimpanan Kriptografi.
Nilai acak untuk tujuan anti-tabrakan. Hasilkan nilai acak (sebaiknya acak secara kriptografi) untuk memastikan bahwa panggilan yang berurutan dalam detik yang sama tidak menghasilkan hash yang sama (1).
Perlukah Stempel Waktu Disertakan dalam Token CSRF untuk Kedaluwarsa?
Mencantumkan stempel waktu sebagai nilai untuk menentukan waktu kedaluwarsa token CSRF merupakan kesalahpahaman umum. Token CSRF bukanlah token akses. Token ini digunakan untuk memverifikasi keaslian permintaan di sepanjang sesi, menggunakan informasi sesi. Sesi baru seharusnya menghasilkan token baru (1).
Pseudo-Code For Implementing HMAC CSRF Tokens
Berikut adalah contoh dalam pseudo-code yang menunjukkan langkah-langkah implementasi yang dijelaskan di atas:
// Gather the values
secret = getSecretSecurely("CSRF_SECRET") // HMAC secret key
sessionID = session.sessionID // Current authenticated user session
randomValue = cryptographic.randomValue(64) // Cryptographic random value
// Create the CSRF Token
message = sessionID.length + "!" + sessionID + "!" + randomValue.length + "!" + randomValue.toHex() // HMAC message payload
hmac = hmac("SHA256", secret, message) // Generate the HMAC hash
// Add the `randomValue` to the HMAC hash to create the final CSRF token.
// Avoid using the `message` because it contains the sessionID in plain text,
// which the server already stores separately.
csrfToken = hmac.toHex() + "." + randomValue.toHex()
// Store the CSRF Token in a cookie
response.setCookie("csrf_token=" + csrfToken + "; Secure") // Set Cookie without HttpOnly flag
Berikut adalah contoh dalam pseudo-kode yang menunjukkan validasi token CSRF setelah dikirim kembali dari klien:
// Get the CSRF token from the request
csrfToken = request.getParameter("csrf_token") // From form field, cookie, or header
// Split the token to get the randomValue
const tokenParts = csrfToken.split(".");
const hmacFromRequest = tokenParts[0];
const randomValue = tokenParts[1];
// Recreate the HMAC with the current session and the randomValue from the request
secret = getSecretSecurely("CSRF_SECRET") // HMAC secret key
sessionID = session.sessionID // Current authenticated user session
message = sessionID.length + "!" + sessionID + "!" + randomValue.length + "!" + randomValue
// Generate the expected HMAC
expectedHmac = hmac("SHA256", secret, message)
// Compare the HMAC from the request with the expected HMAC
if (!constantTimeEquals(hmacFromRequest, expectedHmac)) {
// HMAC validation failed, reject the request
response.sendError(403, "Invalid CSRF token")
logError("Invalid CSRF token", hmacFromRequest, expectedHmac)
return
}
// CSRF validation passed, continue processing the request
// ...
Catatan
Fungsi constantTimeEquals harus digunakan untuk membandingkan HMAC guna mencegah serangan waktu. Fungsi ini membandingkan dua string dalam waktu konstan, terlepas dari berapa banyak karakter yang cocok.