Security logging adalah mencatat kejadian yang penting buat keamanan (siapa, melakukan apa, kapan, dari mana, berhasil atau gagal) dalam bentuk yang bisa dicari mesin, supaya serangan ketahuan dan bisa ditelusuri. Bayangin buku tamu di pos satpam. Buku yang cuma berisi "ada tamu" gak membantu apa-apa. Buku yang mencatat nama, jam, pintu, dan "ditolak masuk" bikin satpam sadar kalau orang yang sama mencoba enam pintu dalam dua menit. Tapi PIN brankas tamu gak pernah ditulis di buku itu, karena buku tamunya sendiri bisa dibaca orang lain.
🧪 Tebakan cepat: log di Lab mula-mula mencatat setiap page view, tapi gak mencatat login gagal. Dalam dua menit, IP
203.0.113.7gagal login enam kali ke akun admin. Alert brute force-nya menyala? Jawabannya ada di bagian "Apa yang dicatat".
Apa yang dicatat
OWASP (Logging Cheat Sheet) menyebut event yang wajib dicatat. Yang paling penting:
- Login gagal (
auth.login_failed): tanda pertama brute force atau credential stuffing. - Akses yang ditolak (
authz.denied): user yang mencoba halaman atau data yang bukan haknya, misalnya user biasa membuka/admin/users. - Login berhasil, logout, reset password, ganti password.
- Input yang gagal validasi dan request yang kena rate limit.
- Tindakan admin: mengubah role, menghapus user, mengubah konfigurasi.
Page view biasa bukan event keamanan; tempatnya di access log web server, bukan di security log.
Log yang gak mencatat login gagal gak punya apa-apa untuk dihitung, jadi enam percobaan tadi lewat tanpa suara dan alert-nya gak menyala. Itu jawaban tebakannya. Di OWASP Top 10 edisi 2021, masalah ini punya nomor sendiri: A09 Security Logging and Monitoring Failures.
Satu baris log terstruktur
Log terstruktur ditulis sebagai JSON, satu event satu baris, dengan field yang tetap. Mesin bisa memfilter "semua auth.login_failed dari IP ini" tanpa menebak-nebak isi kalimat. Di Laravel, channel khusus security dengan format JSON:
// config/logging.php, di dalam 'channels'
'security' => [
'driver' => 'daily',
'path' => storage_path('logs/security.log'),
'level' => 'info',
'days' => 90,
'formatter' => Monolog\Formatter\JsonFormatter::class,
],
// Waktu login gagal
Log::channel('security')->warning('auth.login_failed', [
'email' => $request->input('email'),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'reason' => 'invalid_password',
]);
channel('security')menulis ke file terpisah,security.log, yang disimpan 90 hari (days).JsonFormatterbikin tiap event jadi satu baris JSON.warningadalah level-nya. Pesannya nama event yang tetap, dan detailnya di array context.
Baris yang tertulis di Lab:
{"message":"auth.login_failed","context":{"email":"[email protected]","ip":"203.0.113.7","user_agent":"Mozilla/5.0 (X11; Linux x86_64)","reason":"invalid_password"},"level":300,"level_name":"WARNING","channel":"production","datetime":"2026-10-08T14:03:47.000000+07:00","extra":{}}
Yang gak boleh masuk log
Log dibaca banyak orang dan banyak alat: developer, tim ops, layanan log pihak ketiga. Apa pun yang masuk log ikut terbuka ke mereka semua. Jangan pernah mencatat:
- Password, termasuk yang salah. Password salah sering cuma beda satu huruf dari yang asli.
- Session ID, token, API key: siapa pun yang membacanya bisa memakainya untuk masuk.
- Nomor kartu dan data pribadi sensitif. Kalau perlu, simpan versi tersamar seperti
**** 4242.
Versi pertama kode login di Lab menulis seperti ini:
Log::channel('security')->warning('auth.login_failed', [
'email' => $request->input('email'),
'password' => $request->input('password'), // JANGAN
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'session_id' => $request->session()->getId(), // JANGAN
'reason' => 'invalid_password',
]);
- Baris
passwordmenulis password yang diketik ke file, sebagai teks biasa. - Baris
session_idmenulis kunci sesi; orang yang membaca log bisa menyamar jadi pengunjung itu. - Hapus dua baris itu. Jangan juga mencatat
$request->all()mentah-mentah, karena isinya ikut membawa password.
Dari log ke alert
Log baru berguna kalau ada yang membacanya. SIEM (Security Information and Event Management) adalah sistem yang mengumpulkan log dari banyak sumber (aplikasi, server, firewall, WAF), mencari pola, dan mengirim alert. Contohnya Splunk, Elastic Security dan IBM QRadar; ELK Stack atau Grafana dengan Loki juga sering dipakai untuk mengumpulkan dan memantau log.
Alert brute force di Lab: 5 atau lebih auth.login_failed dari IP yang sama dalam 5 menit. Dengan login gagal tercatat, percobaan kelima dari 203.0.113.7 pada pukul 14.03.30 menyalakan alert. Ambang terlalu rendah bikin alert berisik karena salah ketik biasa; terlalu tinggi bikin serangan lambat lolos.
⚠️ Jebakan yang sering ketemu
- Gak mencatat login gagal dan akses yang ditolak. Tanpa dua event itu, brute force dan percobaan akses ilegal gak kelihatan.
- Mencatat password, token atau session ID. Log jadi sumber kebocoran. Pilih field satu per satu, jangan
$request->all(). - Log berupa kalimat bebas. "Login failed for admin from 203.0.113.7" susah dihitung. Pakai JSON dengan nama event yang tetap.
- Log cuma di server yang sama. Penyerang yang masuk server bisa menghapus jejaknya. Kirim log ke tempat terpisah yang append-only (cuma bisa ditambah), simpan minimal 90 hari.
TL;DR: Security logging mencatat event keamanan sebagai JSON terstruktur: wajib login gagal (auth.login_failed) dan akses yang ditolak (authz.denied), ditambah login berhasil, reset password, validasi gagal dan tindakan admin; page view cukup di access log. Jangan pernah mencatat password, session ID, token atau nomor kartu utuh; pilih field-nya satu per satu. Di Laravel pakai channel security dengan JsonFormatter dan days 90. Log dikirim ke SIEM (Security Information and Event Management) yang menghitung pola dan mengirim alert, misalnya 5 login gagal dari satu IP dalam 5 menit. Tanpa logging dan monitoring, serangan lewat tanpa suara (OWASP A09).