Authentication & Shell History¶
In one sentence
On Linux the "who got in and what did they type" story lives in a handful of text logs (/var/log/auth.log or secure, journald), the binary login databases (wtmp/btmp/lastlog), and per-user shell history files — each easy to read, and each easy for an attacker to tamper with, so you cross-check them against each other and against the file-system timeline.
Authentication logs¶
| File | Distro | Contents |
|---|---|---|
/var/log/auth.log |
Debian/Ubuntu | SSH, sudo, su, PAM, cron auth, login |
/var/log/secure |
RHEL/CentOS/Fedora | same, RHEL family |
journalctl (systemd journal) |
all systemd | structured logs incl. auth (_SYSTEMD_UNIT=sshd.service) — may be the only copy if text logging is off |
/var/log/audit/audit.log |
auditd installed | kernel-level auth, execve, file access — the richest source when present |
What to read from them:
# Successful SSH logins: user, source IP, method (password/publickey)
grep -E "sshd.*Accepted" /var/log/auth.log
# "Accepted publickey for root from 203.0.113.7 port 5123 ssh2: RSA SHA256:..."
# Failed SSH (brute force) — count by source IP
grep -E "sshd.*Failed password" /var/log/auth.log | grep -oE "from [0-9.]+" | sort | uniq -c | sort -rn | head
# sudo usage: who ran what as whom
grep -E "sudo:.*COMMAND" /var/log/auth.log
# "user : TTY=pts/0 ; PWD=/home/user ; USER=root ; COMMAND=/bin/bash"
# su / new sessions / user & group changes
grep -E "su\[|session opened|useradd|usermod|groupadd|passwd" /var/log/auth.log
# From the journal instead (survives text-log deletion)
journalctl -u ssh -o short-iso | grep -Ei "accepted|failed|invalid user"
journalctl _COMM=sudo -o short-iso
Signals: Accepted publickey for root (direct root login should usually be disabled), Failed password bursts (brute force) then an Accepted (successful), Invalid user sweeps (username enumeration), Accepted from an unexpected country/ASN, sudo to root by a user who normally doesn't, and new-account/group events (useradd, added to sudo/wheel).
Login databases (binary)¶
| File | Read with | Contents |
|---|---|---|
/var/log/wtmp |
last |
Successful logins/logouts + reboots, with source & duration |
/var/log/btmp |
lastb (root) |
Failed login attempts |
/var/run/utmp |
who, w |
Currently logged-in users |
/var/log/lastlog |
lastlog |
Last login time per account |
last -Faiw # full timestamps, IPs, no truncation
last -f /path/to/wtmp # a wtmp pulled from an image
lastb -Fai # failed logins (brute-force sources)
who -a ; w # live sessions right now (do this during live response)
These are tamper targets
wtmp/btmp/utmp are binary and attackers zero specific entries with tools (utmpdump edit + re-import, or purpose-built cleaners). A login in auth.log with no matching last entry (or vice-versa), or a suspiciously clean wtmp on a busy server, is itself a finding. journalctl and audit.log are separate copies — compare them.
Shell history¶
| File | Notes |
|---|---|
~/.bash_history |
Bash — only written on clean logout, no timestamps unless HISTTIMEFORMAT was set; size capped by HISTSIZE |
~/.zsh_history |
Zsh — often has epoch timestamps (: 1699999999:0;command) |
~/.local/share/fish/fish_history, ~/.python_history, ~/.mysql_history, ~/.psql_history, ~/.rediscli_history, ~/.lesshst |
Other interactive tools |
/root/.bash_history |
Root's history — always check |
# Every user's bash history in one pass
for h in /home/*/.bash_history /root/.bash_history; do echo "== $h =="; cat "$h" 2>/dev/null; done
# Zsh with timestamps decoded
awk -F';' '/^: [0-9]/{ "date -d @"substr($1,3,10) | getline d; print d" | "$0 }' ~/.zsh_history
Absence of history is a signal, not an all-clear
Attackers routinely disable or wipe history: unset HISTFILE, export HISTSIZE=0, ln -sf /dev/null ~/.bash_history, history -c, or running from a shell that doesn't persist. An empty or symlinked-to-/dev/null history on an actively-used account is suspicious. Fall back to auditd (execve records every command with args), journald, process accounting (lastcomm/sa if acct is enabled), and network/host telemetry.
Cross-check everything¶
A single log lies; the intersection doesn't. Build the picture from all sources and note the disagreements:
auth.logsays user X logged in from IP at time T → doeslast/wtmpagree? Does the shell history show commands right after T? Does the file-system$MFT-equivalent (timestamps) show files created then?- A sudo-to-root in
auth.log→ what did they run (COMMAND=), and does bash history / auditd confirm? - SSH
Accepted publickey→ whose key? Check~/.ssh/authorized_keysfor keys the user didn't add (persistence).
Live-response quick set¶
w ; who -a # who's on now
last -Faiw | head -40 # recent logins
lastb -Fai | head # recent failures
ss -tunap # current connections + owning process
grep -E "Accepted|sudo.*COMMAND|useradd" /var/log/auth.log | tail -50
for h in /home/*/.bash_history /root/.bash_history; do echo "== $h =="; tail -50 "$h"; done