Cara Melindungi Koneksi WebSocket
Web berkembang pesat. Semakin banyak aplikasi web yang dinamis dan imersif dan tidak perlu diperbarui oleh pengguna akhir. Ada dukungan baru untuk teknologi komunikasi latensi rendah seperti WebSockets. Websocket memungkinkan komunikasi real-time antara berbagai client yang terhubung ke server.
Banyak orang tidak menyadari bagaimana melindungi WebSockets dari beberapa serangan yang sangat umum. Mari kita lihat apa itu dan apa yang harus dilakukan untuk melindungi WebSockets.
Aktifkan CORS
WebSocket tidak memiliki CORS bawaan. Artinya, situs web mana pun dapat terhubung ke koneksi WebSocket situs web lain dan berkomunikasi tanpa batasan. Saya tidak akan menjelaskan mengapa demikian, tetapi perbaikan sederhana untuk ini adalah dengan melihat header origin di WebSocket handshake.
Tentu, header Origin dapat dipalsukan oleh penyerang, tetapi untuk mengeksploitasinya, penyerang harus memalsukan header Origin di browser korban, dan di browser modern ada di browser web. Beberapa JavaScript biasa tidak mengizinkan Anda untuk memodifikasi header origin.
Selain itu, ini bukan masalah jika Anda benar-benar mengautentikasi pengguna, sebaiknya menggunakan cookie (lebih lanjut tentang ini di point Otentikasi pengguna sebelum membuat koneksi WS.
Terapkan Rate Limiting
Rate Limiting itu penting. Tanpa ini, client dapat dengan sengaja atau tidak sadar meluncurkan serangan DoS di server. DoS adalah singkatan dari Denial of Service. DoS berarti bahwa satu client membuat server sangat sibuk dan server tidak dapat menangani client lain.
Dalam kebanyakan kasus, mematikan server adalah upaya yang disengaja oleh penyerang. Implementasi frontend yang tidak memadai dapat menyebabkan DoS oleh client biasa.
Untuk menerapkan pembatasan kecepatan di WebSockets, kami menggunakan algoritme ember bocor (yang tampaknya merupakan algoritme yang sangat umum untuk penerapan jaringan).
Idenya adalah bahwa ada ember dengan lubang ukuran tetap di lantai. Saat Anda mulai menuangkan air ke dalamnya, air akan keluar dari lubang di bagian bawah. Nah, jika kecepatan menuangkan air ke dalam ember lebih cepat dari kecepatan keluar dari lubang untuk waktu yang lama, pada titik tertentu ember akan terisi dan mulai bocor. itu saja.
Pahami hubungannya dengan WebSockets:
Air adalah lalu lintas WebSocket yang dikirim oleh pengguna.
Air melewati lubang. Ini berarti bahwa server telah berhasil memproses permintaan WebSocket tertentu.
Air yang masih dalam ember dan belum meluap pada dasarnya adalah lalu lintas yang tertunda. Server akan menangani lalu lintas ini nanti. Ini juga dapat menyebabkan arus lalu lintas meledak (yaitu, tidak masalah jika lalu lintas terlalu banyak dalam waktu yang sangat singkat, selama ember tidak bocor).
Air yang meluap adalah lalu lintas yang dijatuhkan oleh server (terlalu banyak lalu lintas dari satu pengguna)
Intinya di sini adalah Anda perlu melihat aktivitas WebSocket dan menentukan angka-angka ini. Tetapkan ember untuk semua pengguna. Bergantung pada ukuran lubang (waktu rata-rata yang dibutuhkan server untuk memproses satu permintaan WebSocket, seperti menyimpan pesan terkirim), ukuran ember (satu pengguna dapat mengirim selama periode waktu tertentu). Lalu lintas) akan menjadi ditentukan oleh pengguna (ke database).
Ini adalah implementasi yang dikurangi yang digunakan oleh codedamn untuk mengimplementasikan algoritma ember bocor WebSocket. Ada di NodeJS, tetapi konsepnya sama.
if(this.limitCounter >= Socket.limit) {
if(this.burstCounter >= Socket.burst) {
return 'Bucket is leaking'
}
++this.burstCounter
return setTimeout(() => {
this.verify(callingMethod, ...args)
setTimeout(_ => --this.burstCounter, Socket.burstTime)
}, Socket.burstDelay)
}
++this.limitCounter
Jadi apa yang terjadi di sini? Pada dasarnya, jika Anda melebihi batas dan batas burst (konstanta disetel), koneksi WebSocket akan terputus. Jika tidak, itu akan me-reset penghitung burst setelah penundaan tertentu. Ini menyisakan ruang lagi untuk ledakan lain.
Batasi ukuran muatan
Ini harus diimplementasikan sebagai fitur di library WebSocket sisi server. Jika tidak, saatnya untuk mengubahnya menjadi yang lebih baik! Anda perlu membatasi panjang maksimum pesan yang dapat dikirim melalui WebSockets. Secara teoritis tidak ada batasan. Tentu saja, mendapatkan muatan besar kemungkinan besar akan menggantung instance soket tertentu dan menghabiskan lebih banyak sumber daya sistem daripada yang diperlukan.
Misalnya, jika Anda menggunakan library WS untuk node untuk membuat WebSocket di server Anda, Anda dapat menggunakan opsi maxPayload untuk menentukan ukuran muatan maksimum dalam byte. Jika ukuran payload lebih besar dari itu, library akan memutuskan koneksi secara native.
Jangan mencoba menerapkannya sendiri dengan menentukan panjang pesan. Anda tidak harus memuat seluruh pesan ke dalam RAM sistem terlebih dahulu. Jika 1 byte lebih besar dari batas yang ditetapkan, hapus. Ini hanya dapat diimplementasikan oleh library (yang memperlakukan pesan sebagai aliran byte, bukan string tetap).
Buat protokol komunikasi yang solid
Saat ini Anda menggunakan koneksi ganda, jadi Anda mungkin mengirim sesuatu ke server. Server dapat mengirim teks apa pun kembali ke client. Anda perlu memiliki cara untuk komunikasi yang efektif antara keduanya.
Jika Anda ingin memperluas aspek perpesanan situs web Anda, Anda tidak dapat mengirim pesan mentah. Saya lebih suka menggunakan JSON, tetapi ada cara lain yang dioptimalkan untuk mengatur komunikasi. Namun, dengan mempertimbangkan JSON, skema perpesanan dasar untuk situs biasa terlihat seperti ini:
Client to Server (or vice versa): { status: "ok"|"error", event: EVENT_NAME, data: <any arbitrary data> }
Sekarang Anda dapat dengan mudah melihat acara dan format yang valid di server. Jika format pesan berbeda, segera putuskan sambungan dan catat alamat IP pengguna. Format tidak berubah kecuali seseorang secara manual menggelitik koneksi WebSocket Anda. Jika Anda menggunakan node, kami menyarankan Anda menggunakan library Joi untuk lebih memvalidasi data yang diterima dari pengguna Anda.
Otentikasi pengguna sebelum membuat koneksi WS
Jika Anda menggunakan WebSocket untuk pengguna yang diautentikasi, sebaiknya Anda hanya mengizinkan pengguna yang diautentikasi untuk membuat koneksi WebSocket. Jangan menunggu siapa pun untuk membuat koneksi dan kemudian mengautentikasi melalui WebSocket itu sendiri. Pertama-tama, membuat koneksi WebSocket agak mahal. Oleh karena itu, tidak diinginkan bagi orang yang tidak berwenang untuk melompati WebSockets atau memblokir koneksi yang mungkin digunakan orang lain.
Untuk melakukannya, berikan data autentikasi ke WebSocket saat membuat koneksi di ujung depan. X-Auth-Tokenb: Mungkin berupa header seperti <token yang ditetapkan ke client ini saat login>. Secara default, cookie tetap diteruskan.
Sekali lagi, itu benar-benar tergantung pada library yang Anda gunakan di server Anda untuk mengimplementasikan WebSockets. Namun, jika Anda menggunakan Node dan Anda menggunakan WS, Anda dapat menggunakan fungsi verifikasiClient ini untuk mengakses objek informasi yang diteruskan ke koneksi WebSocket. (Sama seperti Anda dapat mengakses objek req dalam permintaan HTTP.)
Gunakan SSL dengan WebSocket
Ini mudah, tetapi saya masih harus mengatakannya. Gunakan wss:// instead of ws://. Ini menambahkan lapisan keamanan ke komunikasi. Gunakan server seperti Nginx untuk proxy terbalik WebSocket dan aktifkan SSL untuk mereka. Menyiapkan Nginx adalah tutorial yang sama sekali berbeda. Bagi mereka yang akrab dengan Nginx, saya akan meninggalkan arahan yang perlu saya gunakan untuk Nginx. Klik disini untuk detail.
location /your-websocket-location/ {
proxy_pass http://127.0.0.1:1337;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
Kami berasumsi bahwa server WebSocket listen pada port 1337 dan pengguna terhubung ke WebSocket dengan cara berikut:
const ws = new WebSocket('wss://yoursite.com/your-websocket-location')