Mengamankan Session
Tutup celah session: fixation, hijacking, timeout, dan remember-me yang aman.
Kenapa Session Perlu Diamankan Khusus?
Session ID adalah kunci kerajaan: siapa pun yang memegangnya dianggap sudah login, tanpa perlu password. Tiga serangan mengincarnya: fixation (penyerang menanam ID ke korban sebelum login), hijacking (penyerang mencuri ID korban, misal via XSS atau sniffing), dan session abadi (ID tidak pernah kedaluwarsa, memperbesar jendela serangan).
<?php
// Contoh 1: pertahanan berlapis di auth.php
session_start();
// 1. Regenerate ID setiap login (anti fixation) dan berkala
if (!isset($_SESSION["regenerasi"])) {
$_SESSION["regenerasi"] = time();
} elseif (time() - $_SESSION["regenerasi"] > 1800) {
session_regenerate_id(true); // ID baru tiap 30 menit
$_SESSION["regenerasi"] = time();
}
// 2. Idle timeout: tendang setelah 30 menit tidak aktif
$timeout = 1800;
if (isset($_SESSION["terakhir_aktif"]) && time() - $_SESSION["terakhir_aktif"] > $timeout) {
session_destroy();
header("Location: login.php?alasan=timeout");
exit;
}
$_SESSION["terakhir_aktif"] = time();
// 3. Ikat session ke user agent (deteksi pencurian kasar)
$ua = $_SERVER["HTTP_USER_AGENT"] ?? "";
if (!isset($_SESSION["ua"])) {
$_SESSION["ua"] = $ua;
} elseif ($_SESSION["ua"] !== $ua) {
session_destroy(); // user agent berubah = kemungkinan hijack
header("Location: login.php?alasan=keamanan");
exit;
}Regenerasi berkala mempersempit jendela ID curian: ID yang dicuri kemarin sudah tidak berlaku. Idle timeout menutup sesi yang ditinggal di komputer umum. Ikatan user-agent bukan sempurna (bisa dipalsukan), tapi menggagalkan serangan oportunis.
<?php
// Contoh 2: remember-me aman dengan token (bukan perpanjang session!)
// Saat login + centang "ingat saya":
function buatRememberToken(int $userId): string {
$token = bin2hex(random_bytes(32)); // token mentah ke cookie
$hash = hash("sha256", $token); // hash ke database!
// simpan: INSERT INTO remember_tokens (user_id, token_hash, kedaluwarsa)
setcookie("ingat", $token, time() + 30 * 86400, "/", "", false, true);
return $token;
}
// Saat session habis tapi cookie "ingat" ada:
function cekRemember(PDO $pdo): void {
if (isset($_SESSION["user_id"]) || empty($_COOKIE["ingat"])) return;
$hash = hash("sha256", $_COOKIE["ingat"]);
// SELECT user_id FROM remember_tokens WHERE token_hash = ? AND kedaluwarsa > NOW()
// jika cocok: login otomatis + regenerate + BUAT TOKEN BARU (rotasi)
}Prinsipnya: cookie menyimpan token mentah, database menyimpan hash-nya (seperti password). Token dirotasi setiap dipakai. Kalau database bocor, penyerang hanya dapat hash yang tidak bisa dipakai sebagai cookie.
Jebakan yang Sering Terjadi
session.use_only_cookies mati. Secara default modern PHP sudah 1, tapi pastikan: ID session tidak boleh dikirim via URL (?PHPSESSID=...) karena URL bocor ke history, log, dan referer. Cek dengan ini_get("session.use_only_cookies").
Cookie session tanpa flag aman. Set di php.ini atau kode: session.cookie_httponly=1 (anti curi via XSS), session.cookie_samesite=Lax (anti CSRF), dan session.cookie_secure=1 di production HTTPS (hanya dikirim via HTTPS).
Deteksi Anomali Sederhana
Selain tiga pertahanan utama, tambahkan deteksi anomali ringan:
<?php
session_start();
// Hitung request per menit per session (anti otomasi)
$menit = (int) date("i");
if (($_SESSION["menit"] ?? -1) !== $menit) {
$_SESSION["menit"] = $menit;
$_SESSION["hitung"] = 1;
} else {
$_SESSION["hitung"]++;
if ($_SESSION["hitung"] > 120) {
// lebih dari 120 request/menit: kemungkinan bot
sleep(2); // perlambat, jangan langsung blokir (hindari false positive)
}
}sleep() sebagai respons lebih elegan daripada blokir langsung: bot melambat drastis, manusia normal tidak merasakan apa-apa (mereka tidak pernah 120 request/menit). Untuk proteksi serius, pindahkan hitungan ke Redis/database agar bertahan antar session.
Catatan teknis: Untuk "logout dari semua perangkat", simpan
session_versiondi database per user dan di session. Saat user ganti password, naikkan versinya: semua session dengan versi lama otomatis tidak valid di guard. Satu kolom, fitur keamanan premium.
Tantangan
Guard dengan idle timeout
Tulis file auth.php yang: memanggil session_start(), menendang ke login.php jika $_SESSION["user_id"] tidak ada ATAU idle lebih dari 1800 detik ($_SESSION["terakhir_aktif"]), lalu memperbarui terakhir_aktif setiap request.