Server yang sudah memverifikasi login tidak mengulangi verifikasi tersebut pada setiap permintaan, tetapi mengandalkan pengenal session (session ID) yang dikirim ulang browser pada permintaan berikutnya. Konsekuensinya tegas, yaitu siapa pun yang memegang pengenal itu diperlakukan sebagai pengguna yang sah, sehingga keamanan seluruh sesi bertumpu pada satu string. Topik ini membahas tiga cara pengenal session gagal, yaitu pembangkitan yang dapat diprediksi, session fixation, dan session hijacking, disertai reproduksi pada lab lokal dan perbaikannya di kode.
Pengenal session sebagai kredensial pembawa#
Pengenal session bekerja sebagai kredensial pembawa (bearer credential), yaitu kredensial yang sahnya ditentukan oleh kepemilikan semata, bukan oleh pengetahuan atau biometrik. Setelah login berhasil, server menyimpan keadaan sesi di sisinya dan menyerahkan penanda kecil kepada browser melalui cookie. Pada setiap permintaan berikutnya, server cukup membaca penanda itu lalu mengambil identitas dan hak akses dari penyimpanannya sendiri. Mekanisme ini nyaman bagi pengguna, tetapi sekaligus memindahkan seluruh nilai dari sebuah login ke satu string yang beredar pada jaringan dan tersimpan di browser.
Sifat pembawa itu menentukan arah serangannya. Penyerang tidak memerlukan kata sandi apabila pengenal dapat ditebak, dapat ditanam, atau dapat dicuri, karena server tidak mampu membedakan pemilik asli dari pemegang pengenal curian. Tiga serangan pada topik ini mengincar celah yang berbeda pada mekanisme yang sama. Pembangkitan yang lemah menyerang ketidakdapatditebakan pengenal, fixation menyerang saat keadaan berganti dari anonim ke terautentikasi, sedangkan hijacking menyerang kerahasiaan pengenal selama sesi berjalan.
Pengenal yang dapat diprediksi#
Skrip berikut membandingkan tiga cara membangkitkan pengenal, yaitu penghitung naik, penyandian base64 dari angka waktu, dan secrets.token_urlsafe(32) dari library standar Python. Dua cara pertama merepresentasikan praktik cacat yang masih dijumpai pada aplikasi nyata, sedangkan cara ketiga adalah praktik yang benar. Nilai acak tidak dicetak utuh, tetapi hanya panjang dan hasil pemeriksaannya, sehingga keluaran dapat diulang secara identik.
"""Membandingkan pengenal session yang dapat diprediksi dengan token acak kriptografis.
Dua kelemahan klasik dimodelkan pada versi cacat:
1. pengenal yang dibangkitkan dari pola (penghitung naik, base64 dari waktu),
2. pengenal yang tidak diregenerasi setelah login (session fixation).
Versi aman memakai secrets.token_urlsafe dan meregenerasi pengenalnya.
Nilai acak tidak dicetak, hanya panjang dan hasil pemeriksaan perubahan,
agar keluaran dapat diulang secara identik.
"""
import base64
import secrets
def pengenal_penghitung(nomor):
"""Pengenal berurutan: polanya dapat diprediksi siapa pun."""
return f"sid-{nomor:04d}"
def pengenal_waktu(waktu):
"""Pengenal dari base64 angka waktu: dapat ditebak bila waktunya dikira."""
return base64.b64encode(str(waktu).encode()).decode()
def login_cacat(penyimpanan, pengenal, nama):
"""Login yang mempertahankan pengenal bawaan permintaan (cacat)."""
penyimpanan[pengenal] = nama # pengenal datang dari cookie, dipakai apa adanya
return pengenal, pengenal
def login_aman(penyimpanan, pengenal, nama):
"""Login yang meregenerasi pengenal setelah keberhasilan (aman)."""
penyimpanan.pop(pengenal, None) # sesi anonim milik pengenal tanaman dibuang
pengenal_baru = secrets.token_urlsafe(32)
penyimpanan[pengenal_baru] = nama
return pengenal, pengenal_baru
print("== Pengenal session yang dapat diprediksi ==")
print("penghitung naik :", ", ".join(pengenal_penghitung(n) for n in (1001, 1002, 1003)))
waktu_contoh = 1730000000
print(f"base64 dari waktu : {pengenal_waktu(waktu_contoh)} (angka waktu {waktu_contoh})")
token = secrets.token_urlsafe(32)
print(f"secrets.token_urlsafe(32): panjang {len(token)} karakter, 32 byte acak kriptografis")
print()
print("== Session fixation ==")
sesi_cacat = {}
sebelum, sesudah = login_cacat(sesi_cacat, pengenal_penghitung(1004), "budi")
print(f"versi cacat: sebelum login {sebelum} -> sesudah login {sesudah} (pengenal tidak berubah)")
sesi_aman = {}
token_tanaman = secrets.token_urlsafe(32)
sesi_aman[token_tanaman] = None # penyerang menanam pengenal anonim
sebelum_aman, sesudah_aman = login_aman(sesi_aman, token_tanaman, "budi")
print(f"versi aman : pengenal diregenerasi setelah login "
f"(berubah: {sebelum_aman != sesudah_aman}, panjang baru {len(sesudah_aman)} karakter)")== Pengenal session yang dapat diprediksi ==
penghitung naik : sid-1001, sid-1002, sid-1003
base64 dari waktu : MTczMDAwMDAwMA== (angka waktu 1730000000)
secrets.token_urlsafe(32): panjang 43 karakter, 32 byte acak kriptografis
== Session fixation ==
versi cacat: sebelum login sid-1004 -> sesudah login sid-1004 (pengenal tidak berubah)
versi aman : pengenal diregenerasi setelah login (berubah: True, panjang baru 43 karakter)Penghitung naik menghasilkan sid-1001, sid-1002, dan seterusnya, sehingga pengenal berikutnya dapat ditebak dari pengenal yang pernah terlihat. Penyandian base64 dari angka waktu tampak acak bagi mata yang tidak terlatih, tetapi keluaran memperlihatkan bahwa MTczMDAwMDAwMA== hanyalah 1730000000 yang disandikan ulang, sehingga penyerang cukup mencoba rentang waktu yang wajar untuk mendapatkan seluruh kandidatnya. Sebaliknya, token_urlsafe(32) mengambil 32 byte dari pembangkit acak kriptografis sehingga jumlah kandidatnya mencapai , angka yang membuat penebakan mustahil secara praktis. Panjang 43 karakter pada keluaran hanyalah panjang penyandian base64url dari 32 byte itu, bukan ukuran kekuatannya.
Session fixation#
Session fixation adalah serangan yang menanam pengenal pilihan penyerang ke browser korban sebelum login, lalu menunggu korban melakukan autentikasi pada pengenal tersebut. Pengenal tanaman dapat diselipkan melalui tautan yang membawa parameter sesi, melalui subdomain yang berhak menulis cookie domain induk, atau melalui server yang membiarkan pengenal dibuat dari permintaan. Apabila server tidak meregenerasi pengenal ketika korban berhasil login, pengenal tanaman berganti status dari sesi anonim menjadi sesi terautentikasi, dan penyerang yang memegang salinannya masuk tanpa menyentuh kata sandi sama sekali.
Skrip di atas memodelkan kejadian itu pada dua versi login. Versi cacat menyimpan sesi dengan pengenal bawaan permintaan apa adanya, sedangkan versi aman membuang sesi anonim milik pengenal tanaman sebelum menerbitkan pengenal baru. Diagram berikut merangkum kedua alurnya.
Pada versi cacat, keluaran skrip memperlihatkan sid-1004 bertahan dari sebelum hingga sesudah login, sehingga penyerang dan korban berbagi sesi yang sama. Pada versi aman, fungsi login_aman menghapus entri pengenal tanaman dari penyimpanan server sebelum menerbitkan pengenal baru, sehingga salinan yang dipegang penyerang menunjuk ke entri yang sudah tidak ada. Pemeriksaan berubah: True pada keluaran adalah sifat yang wajib hadir pada setiap pergantian tingkat akses, bukan hanya pada saat login.
Pembajakan session dan pertahanan berlapis#
Session hijacking adalah pencurian pengenal dari sesi yang sedang berjalan, dan jalur-jalur utamanya sudah tersingkap pada pembahasan atribut cookie. Skrip yang disisipkan melalui celah XSS membaca document.cookie ketika HttpOnly absen, penyadap pada jaringan membaca cookie ketika Secure absen, sedangkan pengenal yang tertanam pada URL bocor melalui log server, header Referer, dan riwayat browser. Pertahanannya berlapis, yaitu atribut cookie mempersempit jalur pencurian, masa berlaku yang pendek membatasi jendela pemanfaatan, dan kemampuan mencabut sesi di sisi server memastikan pengenal yang bocor dapat dimatikan sebelum masa berlakunya habis.
Dua kebiasaan server menutup sisa celahnya. Pertama, regenerasi pengenal pada setiap pergantian tingkat akses, terutama dari anonim ke terautentikasi dan dari pengguna biasa ke administrator, karena pada pergantian itulah sesi menjadi bernilai bagi penyerang. Kedua, pengikatan sinyal perangkat, misalnya mencatat user-agent dan alamat IP saat sesi dibuat lalu menuntut verifikasi ulang ketika sinyalnya berubah drastis, sebagai deteksi dini pencurian. Kedua kebiasaan itu disarankan dalam Session Management Cheat Sheet milik OWASP, dan kegagalan pada area ini tercatat sebagai A07 Authentication Failures pada OWASP Top 10:2025.
Kesalahan umum#
- Menempatkan pengenal session pada URL agar mudah dikirim lewat tautan. Pengenal yang berada di URL bocor melalui log server, header
Referer, dan riwayat browser, sehingga pengenal ditempatkan pada cookie beratributHttpOnly,Secure, danSameSite. - Meregenerasi pengenal tanpa membuang sesi lamanya. Server yang hanya menambah entri baru justru menumpuk sesi hidup dan membiarkan tanaman penyerang berfungsi, sehingga entri lama wajib dihapus pada saat yang sama.
- Menggunakan pembangkit acak umum seperti modul
randomPython untuk pengenal. Deretnya dapat diprediksi dari beberapa keluaran sebelumnya, sehingga hanya modulsecretsatau padanan kriptografisnya yang layak digunakan. - Menganggap sesi aman hanya karena pengenalnya panjang. Prediktabilitas ditentukan oleh sumber acak, sedangkan pencurian ditentukan oleh atribut cookie, dan keduanya tidak saling menggantikan.
Ringkasan#
- Pengenal session adalah kredensial pembawa, sehingga pengenal yang dapat ditebak, ditanam, atau dicuri bernilai sama dengan akun yang diretas.
- Pengenal wajib dibangkitkan dari pembangkit acak kriptografis, dan 32 byte acak dengan kemungkinannya membuat penebakan tidak layak dilakukan.
- Regenerasi pengenal pada setiap pergantian tingkat akses menutup session fixation, asalkan sesi lamanya dibuang, bukan dibiarkan hidup.
- Masa berlaku pendek, atribut cookie pelindung, dan pencabutan sesi di server mempersempit dampak pembajakan session.
Referensi#
- Session Management Cheat Sheet — OWASP — Panduan panjang, rotasi, dan pengakhiran session ID yang menjadi dasar pertahanan topik ini.
- A07:2025 Authentication Failures — OWASP Top 10 — Kategori OWASP Top 10:2025 yang mencakup kegagalan pengelolaan session dan autentikasi.
- Authentication vulnerabilities — PortSwigger — Materi kerentanan autentikasi aplikasi web beserta cara mengujinya secara sistematis.