Dulu saya berpikir bahwa menulis lapisan autentikasi sendiri adalah sebuah kebanggaan tersendiri. Jika Anda memahami JWT dan bisa melakukan hashing kata sandi, seberapa sulit itu? Saya membangun backend Node, menerbitkan token, dan menganggap semuanya selesai. Kodenya berjalan. Tes lulus. Kemudian saya mulai membaca tentang bagaimana serangan nyata sebenarnya terjadi, dan saya tersadar betapa rapuhnya sistem tersebut. Setiap bab tentang injeksi, enumerasi, dan kebocoran side-channel membuat saya kembali ke editor dengan perasaan cemas. Sistem autentikasi saya tidak hanya memiliki celah; ia memiliki empat pintu terbuka lebar yang saya pasang sendiri. Inilah yang saya temukan, dan tepatnya apa yang saya ubah.
SQL Injection melalui Konkatenasi String
Bug pertama adalah trik paling klasik. Saya mengambil input pengguna dan memasukkannya langsung ke dalam string SQL. Dalam rute login saya, saya mengambil email dari request body dan menggabungkannya ke dalam kueri seperti SELECT * FROM users WHERE email = '${email}'. Rasanya tidak berbahaya karena saya mengontrol frontend. Itu adalah asumsi yang berbahaya. Penyerang tidak membutuhkan frontend Anda. Satu body POST yang dirancang khusus dapat mengubah pemeriksaan login tersebut menjadi pelanggaran data atau penghapusan database. Kirimkan payload seperti ' OR '1'='1 atau lebih buruk lagi, stacked query yang menghapus tabel, dan jika string tersebut dieksekusi secara mentah, data Anda akan hilang. Saya tidak memberi database cara untuk membedakan antara kode saya dan data penyerang.
Solusinya bukanlah validasi input tambahan atau melakukan escaping string secara manual. Solusi sebenarnya adalah kueri berparameter (parameterized queries). Saya beralih ke node-postgres dan mulai menggunakan placeholder seperti $1. Kueri tersebut menjadi sebuah templat: SELECT * FROM users WHERE email = $1. Driver mengirimkan SQL dan nilainya melalui saluran yang terpisah. Database memperlakukan input tersebut murni sebagai data, tidak peduli karakter apa pun yang dikandungnya. Satu perubahan ini menutup seluruh kelas serangan injeksi. Ini lebih mudah dibaca, lebih mudah dipelihara, dan menghilangkan beban untuk menjadi ahli regex setiap kali Anda menulis klausa WHERE.
Enumerasi Email Melalui Pesan Kesalahan
Kesalahan kedua saya tampak seperti UX yang baik. Ketika pengguna mengetik email yang salah, saya mengembalikan User not found. Ketika mereka memasukkan email yang benar tetapi kata sandi salah, saya mengembalikan Incorrect password. Rasanya sangat membantu. Namun, itu juga merupakan alat pengintaian bagi penyerang. Skrip enumerasi dapat membombardir endpoint login Anda dengan ribuan alamat email. Jika body respons atau kode status berubah tergantung pada apakah akun tersebut ada atau tidak, skrip tersebut dapat membangun daftar pengguna Anda yang terverifikasi. Daftar tersebut menjadi dasar untuk credential stuffing, phishing terarah, dan upaya brute-force lebih lanjut.
Saya harus menerima bahwa keramahan pengguna terkadang harus mengalah demi keamanan. Saya mengubah setiap jalur login yang gagal untuk mengembalikan string yang persis sama: Invalid credentials. Tanpa petunjuk. Tanpa logika percabangan dalam respons kesalahan. Baik email tidak ditemukan, kata sandi salah, atau akun terkunci, teksnya tetap identik. Ini juga berlaku untuk alur registrasi dan reset kata sandi; jangan ungkapkan apakah sebuah alamat sudah ada di sistem Anda. Satu pesan generik menghilangkan kebocoran informasi yang diandalkan oleh penyerang.
Serangan Timing dalam Perbandingan Kata Sandi
Bug ketiga tidak terlihat. Saya
