Session fixation terjadi ketika penyerang membuat atau memaksa korban memakai session ID yang sudah diketahui penyerang, lalu menunggu korban login. Pencegahan utamanya adalah rotasi session ID pada momen perubahan status keamanan, mengonfigurasi cookie dengan benar, dan memastikan session ID lama tidak lagi valid.

Artikel ini fokus pada keputusan teknis di backend: kapan session ID harus di-regenerate, bagaimana menyimpan dan membatalkan sesi lama, atribut cookie apa yang wajib dipakai, serta bagaimana menguji implementasinya secara manual dan otomatis.

Bagaimana session fixation terjadi

Dalam aplikasi berbasis session, server biasanya menyimpan data sesi di server-side store, sementara browser menyimpan session ID di cookie. Jika session ID sebelum login tetap dipakai setelah login, penyerang dapat mengeksploitasi celah ini.

Contoh alur serangan

  1. Penyerang mendapatkan session ID yang valid dari aplikasi, misalnya dengan membuka halaman publik.
  2. Penyerang membuat korban memakai session ID tersebut. Caranya bisa melalui link yang menyisipkan session ID, subdomain yang dapat menulis cookie pada domain induk, kelemahan XSS, atau konfigurasi cookie yang terlalu longgar.
  3. Korban login menggunakan session ID yang sama.
  4. Jika server tidak mengganti session ID setelah login, session ID tersebut kini terasosiasi dengan akun korban.
  5. Penyerang memakai session ID yang sudah diketahui untuk mengakses akun korban.

Catatan: jika aplikasi hanya menerima session ID dari cookie yang aman dan tidak pernah menerima session ID dari URL atau parameter request, risiko berkurang. Namun rotasi session ID tetap wajib karena session ID bisa bocor lewat jalur lain, termasuk XSS, proxy log, atau kesalahan konfigurasi domain cookie.

Kapan session ID wajib di-regenerate

Session ID harus di-regenerate setiap kali terjadi perubahan tingkat kepercayaan terhadap sesi. Prinsipnya: session ID anonim tidak boleh menjadi session ID terautentikasi, dan session ID dengan privilege rendah tidak boleh naik privilege tanpa identitas sesi baru.

1. Setelah login berhasil

Ini adalah titik paling penting. Setelah kredensial diverifikasi, buat session ID baru, pindahkan data yang benar-benar diperlukan, simpan status autentikasi pada sesi baru, lalu invalidasi sesi lama.

Jangan hanya menambahkan userId ke sesi yang sudah ada. Itu membuat session ID pra-login tetap bernilai setelah login.

2. Setelah privilege escalation

Privilege escalation mencakup aksi seperti masuk ke area admin, menjalankan sudo mode, mengubah pengaturan keamanan, atau mengakses data sensitif setelah verifikasi ulang. Setelah verifikasi tambahan berhasil, regenerate session ID agar session lama tidak otomatis mewarisi privilege baru.

Contoh: pengguna biasa membuka panel admin dan diminta memasukkan ulang password atau MFA. Setelah verifikasi berhasil, sesi admin harus memakai session ID baru, dengan metadata seperti elevatedAt, elevationMethod, dan masa berlaku privilege yang lebih pendek.

3. Setelah reset password atau pemulihan akun

Reset password mengubah asumsi keamanan akun. Setelah password berhasil direset, buat sesi baru untuk pengguna jika langsung login, dan pertimbangkan untuk membatalkan sesi lain milik akun tersebut. Ini mencegah sesi lama yang mungkin sudah dikompromikan tetap aktif.

4. Setelah perubahan faktor autentikasi atau email utama

Perubahan MFA, nomor telepon pemulihan, email utama, atau metode login eksternal juga layak memicu rotasi session ID. Pada sistem berisiko tinggi, aksi ini juga dapat memicu revokasi sesi lain.

Alur implementasi framework-agnostic

Detail API berbeda antar framework, tetapi alur amannya sama. Implementasi harus memastikan pembuatan session ID baru dan pembatalan session ID lama terjadi secara atomik sejauh mungkin.

Alur login yang aman

  1. Terima kredensial dari request HTTPS.
  2. Validasi kredensial tanpa mengubah status sesi terlebih dahulu.
  3. Ambil session ID lama dari cookie, jika ada.
  4. Buat session ID baru menggunakan generator kriptografis yang kuat.
  5. Buat record sesi baru di server-side session store.
  6. Salin hanya data pra-login yang aman, misalnya flash message atau CSRF seed jika desain aplikasi memang membutuhkannya.
  7. Simpan data autentikasi pada sesi baru: user ID, waktu login, alamat IP atau user agent sebagai metadata audit, bukan sebagai satu-satunya kontrol keamanan.
  8. Invalidate atau revoke session ID lama.
  9. Kirim cookie baru dengan atribut keamanan yang benar.
  10. Catat audit log untuk rotasi sesi.
function rotateSessionAfterAuth(request, response, userId, reason) {
  oldSessionId = request.cookies['__Host-session']
  oldSessionData = oldSessionId ? sessionStore.get(oldSessionId) : null

  newSessionId = crypto.randomToken(32)
  newSessionData = {
    userId: userId,
    authenticatedAt: now(),
    lastSeenAt: now(),
    csrfSeed: oldSessionData?.csrfSeed,
    rotatedFromHash: oldSessionId ? hash(oldSessionId) : null,
    rotationReason: reason
  }

  transaction(() => {
    sessionStore.create(newSessionId, newSessionData, IDLE_TIMEOUT)

    if (oldSessionId) {
      sessionStore.revoke(oldSessionId, {
        reason: reason,
        revokedAt: now(),
        replacedByHash: hash(newSessionId)
      })
    }
  })

  response.setCookie('__Host-session', newSessionId, {
    httpOnly: true,
    secure: true,
    sameSite: 'Lax',
    path: '/',
    maxAge: SESSION_MAX_AGE
  })

  auditLog.write({
    event: 'session_rotated',
    userId: userId,
    reason: reason,
    oldSessionHash: oldSessionId ? hash(oldSessionId) : null,
    newSessionHash: hash(newSessionId),
    ip: request.ip,
    userAgent: request.userAgent,
    occurredAt: now()
  })
}

Contoh di atas bersifat pseudocode. Pada framework tertentu, gunakan API resmi untuk regenerate session jika tersedia, tetapi tetap verifikasi perilakunya: apakah session ID lama benar-benar tidak valid, apakah data sensitif ikut tersalin, dan apakah cookie baru dikirim dengan atribut yang benar.

Data apa yang boleh disalin dari sesi lama?

Saat rotasi, jangan menyalin seluruh isi sesi secara buta. Sesi anonim sering berisi data yang tidak cocok untuk sesi autentikasi.

  • Boleh dipertimbangkan: flash message, preferensi locale, CSRF seed jika desain CSRF token bergantung pada sesi dan token akan diperbarui dengan aman.
  • Hindari disalin: flag privilege, hasil verifikasi MFA lama, state reset password, token OAuth sementara, data checkout sensitif, atau data yang berasal dari parameter request tanpa validasi.
  • Lebih aman: buat sesi baru minimalis, lalu isi ulang data dari database berdasarkan user ID.

Konfigurasi cookie yang aman

Rotasi session ID tidak cukup jika cookie mudah dicuri, dikirim di konteks yang salah, atau dapat ditimpa oleh subdomain. Cookie session harus dikonfigurasi secara eksplisit.

Atribut cookie yang direkomendasikan

Set-Cookie: __Host-session=<session-id>; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=<seconds>
  • HttpOnly: mencegah JavaScript membaca cookie melalui document.cookie. Ini tidak menghapus risiko XSS sepenuhnya, tetapi mengurangi peluang pencurian session ID langsung.
  • Secure: cookie hanya dikirim melalui HTTPS. Tanpa ini, session ID dapat bocor pada koneksi HTTP.
  • SameSite: mengurangi pengiriman cookie pada request lintas situs. Lax biasanya cocok untuk aplikasi web biasa karena masih mendukung navigasi top-level. Strict lebih ketat tetapi dapat mengganggu alur dari link eksternal. None hanya dipakai jika benar-benar butuh konteks cross-site, misalnya embed atau SSO tertentu, dan harus dikombinasikan dengan Secure.
  • Path=/: membatasi cakupan path secara jelas. Dengan prefix __Host-, cookie harus menggunakan Path=/, Secure, dan tidak memakai atribut Domain.
  • Max-Age atau Expires: menentukan umur cookie di browser. Tetap gunakan timeout server-side karena cookie yang belum kedaluwarsa tidak berarti sesi harus valid selamanya.

Session cookie vs persistent cookie

Untuk sesi login biasa, gunakan umur yang masuk akal dan enforce timeout di server. Bedakan antara:

  • Idle timeout: sesi berakhir jika tidak aktif selama periode tertentu.
  • Absolute timeout: sesi berakhir setelah durasi maksimum sejak dibuat, meskipun masih aktif.
  • Remember me: jangan memakai session ID yang sama untuk fitur ini. Gunakan token terpisah, simpan hash token di server, dan rotasi token setelah dipakai.

Jangan mengandalkan expiry cookie saja. Penyerang yang sudah memiliki session ID masih bisa mencoba menggunakannya sampai server-side session store menolaknya.

Invalidasi session lama dan penanganan race condition

Setelah rotasi, session ID lama harus dianggap tidak valid. Ini adalah bagian yang sering terlewat: banyak implementasi membuat session ID baru tetapi tidak benar-benar menghapus atau menandai session ID lama di store.

Strategi invalidasi

  • Delete: hapus record session lama dari store. Sederhana, tetapi audit terhadap penggunaan ulang menjadi lebih sulit jika tidak ada log terpisah.
  • Revoke marker: tandai session lama sebagai revoked dengan alasan dan waktu. Cocok jika ingin mendeteksi penggunaan ulang session ID lama.
  • Session version per user: simpan versi sesi di database user dan session store. Berguna untuk membatalkan banyak sesi sekaligus, misalnya setelah reset password.

Untuk aplikasi dengan banyak server, pastikan semua instance membaca dari session store yang konsisten, misalnya database atau cache terpusat. Jika session disimpan di memori lokal per instance, rotasi dan invalidasi bisa tidak konsisten saat request berikutnya masuk ke instance berbeda.

Race condition setelah login

Browser bisa mengirim beberapa request paralel saat login atau redirect. Jika request lama masih memakai session ID lama, server harus menolak atau mengarahkannya untuk memuat ulang state terbaru. Hindari grace period panjang karena dapat membuka kembali celah fixation.

Jika harus memberi toleransi sangat singkat demi kompatibilitas, pastikan session lama tidak pernah memiliki status autentikasi atau privilege baru. Grace period hanya boleh membantu transisi teknis, bukan memberi akses.

Audit log yang berguna untuk investigasi

Audit log membantu mendeteksi anomali dan membuktikan bahwa rotasi berjalan. Jangan menyimpan session ID mentah di log. Simpan hash menggunakan algoritma yang sesuai untuk korelasi internal, atau gunakan identifier audit terpisah.

Event yang sebaiknya dicatat

  • session_created untuk sesi anonim atau sesi baru.
  • session_rotated saat login, privilege escalation, reset password, atau perubahan faktor autentikasi.
  • session_revoked saat logout, reset password, atau kebijakan keamanan.
  • revoked_session_reused saat session ID lama dipakai lagi setelah invalidasi.
  • cookie_missing_or_invalid jika perlu untuk debugging, dengan rate limiting agar tidak membanjiri log.

Metadata yang berguna: user ID, hash session lama dan baru, alasan rotasi, timestamp, IP, user agent, request ID, dan hasil aksi. Perlakukan IP dan user agent sebagai sinyal audit, bukan bukti identitas yang kuat karena keduanya dapat berubah atau dipalsukan.

Jebakan umum di backend

  • Mengganti cookie tetapi tidak mengganti record server-side. Ini hanya kosmetik jika session ID lama masih diterima.
  • Regenerate sebelum login lalu memakai sesi yang sama setelah login. Rotasi harus terjadi setelah autentikasi berhasil dan sebelum status login dianggap aktif.
  • Menerima session ID dari URL. Session ID di URL mudah bocor ke log, history, referrer, dan monitoring tools.
  • Cookie memakai atribut Domain terlalu luas. Subdomain yang kurang aman dapat menimpa cookie untuk domain induk. Pertimbangkan prefix __Host- jika cocok dengan arsitektur domain.
  • Logout hanya menghapus cookie di browser. Server juga harus membatalkan session ID.
  • Token CSRF tidak diperbarui setelah rotasi. Jika token CSRF terikat sesi, pastikan token lama tidak membuat state baru menjadi tidak konsisten.
  • Session store tidak punya TTL. Sesi yang tidak pernah kedaluwarsa memperbesar dampak kebocoran.
  • Mencatat session ID mentah. Log sering tersebar ke banyak sistem; jangan menjadikannya sumber kebocoran kredensial.

Checklist pengujian manual

Gunakan browser devtools, proxy seperti mitmproxy atau Burp Suite, dan akses ke session store atau log aplikasi jika memungkinkan.

  1. Sebelum login: buka aplikasi, catat nilai cookie session anonim.
  2. Setelah login: pastikan nilai cookie session berubah.
  3. Session lama: kirim request autentikasi menggunakan cookie lama. Server harus menolak, menganggap anonim, atau meminta login ulang.
  4. Privilege escalation: masuk sebagai user biasa, lakukan verifikasi ulang untuk akses sensitif, pastikan session ID berubah lagi.
  5. Reset password: reset password, pastikan sesi lama tidak lagi dapat mengakses akun. Jika kebijakan aplikasi membatalkan semua sesi, verifikasi semua device terkena dampaknya.
  6. Logout: setelah logout, gunakan kembali cookie lama. Server tidak boleh menerima session tersebut.
  7. Atribut cookie: periksa HttpOnly, Secure, SameSite, Path, dan expiry.
  8. Session ID di URL: coba tambahkan parameter seperti ?session=... atau nama parameter lama. Aplikasi tidak boleh menggunakannya sebagai sumber session ID.
  9. Audit log: pastikan event rotasi dan penggunaan session revoked tercatat tanpa session ID mentah.

Checklist pengujian otomatis

Pengujian otomatis sebaiknya mencakup unit test untuk session service dan integration test pada endpoint autentikasi. Fokus pada perilaku, bukan detail framework.

Contoh skenario integration test

test('login rotates session id and revokes old session', async () => {
  anonymous = await client.get('/login')
  oldCookie = anonymous.cookies['__Host-session']

  loginResponse = await client.post('/login', {
    cookies: { '__Host-session': oldCookie },
    body: { email: '[email protected]', password: 'correct-password' }
  })

  newCookie = loginResponse.cookies['__Host-session']
  assert(newCookie != oldCookie)
  assert(await sessionStore.isRevoked(oldCookie) == true)

  reusedOldSession = await client.get('/account', {
    cookies: { '__Host-session': oldCookie }
  })

  assert(reusedOldSession.status == 401 || reusedOldSession.status == 302)
})

Jenis test yang disarankan

  • Unit test: session service membuat ID baru, menyimpan TTL, dan menandai sesi lama revoked.
  • Integration test: endpoint login, logout, reset password, dan privilege escalation benar-benar mengirim cookie baru.
  • Security regression test: session ID lama tidak dapat mengakses endpoint yang butuh autentikasi.
  • Cookie assertion: response memiliki atribut HttpOnly, Secure, SameSite, dan expiry yang sesuai.
  • Concurrency test: beberapa request paralel setelah rotasi tidak membuat sesi lama ikut terautentikasi.
  • Audit test: event rotasi tercatat dan tidak mengandung session ID mentah.

Ringkasan keputusan teknis

Mencegah session fixation membutuhkan lebih dari sekadar cookie aman. Backend harus memperlakukan rotasi session ID sebagai bagian dari boundary keamanan: login, privilege escalation, reset password, dan perubahan faktor autentikasi harus menghasilkan session ID baru.

Implementasi yang kuat memiliki tiga karakteristik: session ID lama dibatalkan di server, cookie baru dikirim dengan atribut keamanan yang tepat, dan audit log cukup detail untuk mendeteksi penggunaan ulang session yang sudah revoked. Jika ketiganya diuji secara otomatis, risiko regresi pada fitur autentikasi akan jauh lebih mudah dikendalikan.