SSH Artifacts: authorized_keys, known_hosts and sshd Logs
OpenSSH artifacts on Linux (authorized_keys, known_hosts, configs, host keys, sshd logs) that prove remote access, key persistence and lateral movement.
- Location
- ~/.ssh/authorized_keys
- Proves
- Which keys can log in to an account, where a user connected to, and who logged in over SSH from where
- Timestamps
- Key files: file system times only; logs: syslog local time or journal usec since Unix epoch (UTC)
- Access
- root (or the account owner for its own ~/.ssh); adm/systemd-journal group for logs
- Retention
- Key files until deleted; log lines follow syslog rotation and journal limits
- Collection
- UAC, Velociraptor, cp -a, journalctl
Tools
Compare all tools- Hash ExtractorIn browser
- Linux Log ParserIn browser
- ssh-keygenCLI · built into Linux
- VelociraptorPlatform · open source
- UACCLI · open source
- grep / zgrepCLI · built into Linux
What it is
OpenSSH leaves evidence on both ends of a connection. On the server side, authorized_keys decides which public keys may log in to an account, sshd_config sets the rules, and sshd logs every accepted and failed attempt. On the client side, known_hosts records the servers a user has connected to and ~/.ssh/config often names them. Together they answer the two core questions of most Linux intrusions: how did the attacker get in, and where did they go next.
Adding a key to authorized_keys is one of the most common Linux persistence techniques (MITRE ATT&CK T1098.004) because it survives password resets.
Where it lives
| Artifact | Path | Notes |
|---|---|---|
| Authorized keys | ~/.ssh/authorized_keys, ~/.ssh/authorized_keys2 | Default AuthorizedKeysFile; check every home including /root and service accounts |
| Known hosts | ~/.ssh/known_hosts, /etc/ssh/ssh_known_hosts | Client-side record of servers |
| Hash backup | ~/.ssh/known_hosts.old | Left by ssh-keygen -H when a file is hashed |
| Client config | ~/.ssh/config, /etc/ssh/ssh_config, /etc/ssh/ssh_config.d/ | Host aliases, users, jump hosts, keys |
| User keys | ~/.ssh/id_*, ~/.ssh/id_*.pub | Note presence and fingerprints only |
| Per-login scripts | ~/.ssh/rc, /etc/ssh/sshrc | Run by sshd before the user's shell |
| User environment | ~/.ssh/environment | Only read if PermitUserEnvironment allows it |
| Server config | /etc/ssh/sshd_config, /etc/ssh/sshd_config.d/*.conf | Drop-ins via Include in current Debian, Ubuntu and Fedora/RHEL 9 packaging |
| Host keys | /etc/ssh/ssh_host_{rsa,ecdsa,ed25519}_key(.pub) | Identify the server; regenerated keys change client fingerprints |
| Logs | /var/log/auth.log (Debian/Ubuntu), /var/log/secure (RHEL family), systemd journal | Facility AUTH by default; RHEL-family configs set AUTHPRIV |
What it proves
- Access capability: any key in
authorized_keyscan log in as that account, subject to its options andsshd_config. - Actual logins:
Accepted publickeyorAccepted passwordlines prove a successful authentication, with source IP, port and (for keys) the key fingerprint. - Attempts:
Failed password,Invalid userand similar lines show brute force and username guessing. - Outbound movement:
known_hostsand~/.ssh/configshow that the account connected to other hosts, even with no shell history. - Not proven: a key comment (
user@host) is free text written by whoever created the key.known_hostsentries do not carry dates or show that a login succeeded, only that a host key was accepted.
Key fields
authorized_keys options (comma-separated, before the key type):
| Option | Meaning |
|---|---|
command="..." | Forces a command for this key; original request in SSH_ORIGINAL_COMMAND |
from="pattern" | Restricts source hosts/addresses (CIDR allowed) |
environment="NAME=value" | Sets variables if PermitUserEnvironment is enabled |
no-pty, no-port-forwarding, no-agent-forwarding, no-X11-forwarding, no-user-rc | Restrictions |
restrict | All restrictions at once; can be relaxed with pty, port-forwarding etc. |
permitopen=, permitlisten=, tunnel= | Forwarding and tunnel controls |
expiry-time="YYYYMMDD[HHMM[SS]][Z]" | Key stops working after this time |
cert-authority, principals= | Trust a CA instead of a single key |
known_hosts: [marker] hostnames keytype base64key [comment]. Hashed entries start with |1| (salt and HMAC), so hostnames cannot be read directly. Debian and Ubuntu ship HashKnownHosts yes in /etc/ssh/ssh_config; upstream default is no.
sshd_config directives: AuthorizedKeysFile, AuthorizedKeysCommand, AuthorizedPrincipalsFile, TrustedUserCAKeys, PermitRootLogin (default prohibit-password), PasswordAuthentication, PermitUserEnvironment (default no), ForceCommand, Match blocks, LogLevel (default INFO), SyslogFacility.
Log lines (process sshd, and on OpenSSH 9.8+ also sshd-session; on 10.0+ some messages may also come from sshd-auth):
Accepted publickey for deploy from 198.51.100.7 port 51522 ssh2: ED25519 SHA256:<fingerprint>
Accepted password for deploy from 203.0.113.50 port 40318 ssh2
Failed password for invalid user admin from 203.0.113.50 port 40022 ssh2
Invalid user admin from 203.0.113.50 port 40022
Timestamps
Key and config files have only file system times: mtime of authorized_keys changes whenever a key is added or removed, and ctime cannot be set with touch. Syslog files (auth.log, secure) use the local time zone and, in the classic format, no year; the journal stores __REALTIME_TIMESTAMP in microseconds since 1970-01-01 UTC. expiry-time values without Z are interpreted in the system time zone.
Retention
Key files and configs remain until changed. Log retention depends on logrotate (weekly with four rotations is a common Debian default for auth.log) and journal size limits; see syslog and auth.log and systemd journal. known_hosts only grows unless a user runs ssh-keygen -R or edits it.
Collection
UAC has dedicated artifacts for authorized_keys*, known_hosts, public keys and ~/.ssh/rc, and collects /etc/ssh with /etc. Velociraptor provides Linux.Ssh.AuthorizedKeys, Linux.Ssh.KnownHosts and Linux.Syslog.SSHLogin.
R=/mnt/evidence
tar -C "$R" -czf ssh.tgz etc/ssh root/.ssh home/*/.ssh 2>/dev/null # includes private keys: handle as sensitive
journalctl -D "$R"/var/log/journal _COMM=sshd -o short-iso # add _COMM=sshd-session on newer OpenSSH
Treat private host and user keys as sensitive evidence: record fingerprints, restrict access, never reuse them.
Parsing
# fingerprint every authorized key, then match against "Accepted publickey" lines
for f in /mnt/evidence/root/.ssh/authorized_keys* /mnt/evidence/home/*/.ssh/authorized_keys*; do
echo "== $f"; ssh-keygen -l -f "$f"; done
grep -hE 'sshd(-session|-auth)?\[[0-9]+\]: (Accepted|Failed|Invalid user)' /mnt/evidence/var/log/auth.log* /mnt/evidence/var/log/secure*
zgrep -h 'Accepted' /mnt/evidence/var/log/auth.log.*.gz
# test a candidate hostname against a hashed known_hosts
ssh-keygen -F 10.0.0.12 -f /mnt/evidence/home/alice/.ssh/known_hosts
Linux Log Parser reads auth.log / secure, syslog, journal files, audit.log and wtmp / btmp / lastlog in the browser and merges them into one timeline with rebuilt login sessions; nothing is uploaded.
Investigator tips
- Match the
SHA256:fingerprint in eachAccepted publickeyline to a specific key line; this identifies which key an attacker used even when comments are fake. - Look for
AuthorizedKeysFilepointing to an unusual path, or anAuthorizedKeysCommand, insshd_configand every drop-in: keys may live outside~/.ssh. command=keys and~/.ssh/rcrun code at each login; afrom=option can reveal attacker infrastructure.- Parsers or SIEM rules that match only
sshd[miss lines taggedsshd-sessionorsshd-authon recent OpenSSH (Velociraptor'sLinux.Syslog.SSHLoginfilters on programsshd). - Hashed
known_hostscan still be tested against candidate IPs from your case withssh-keygen -F. - Changed host keys (new ctime on
/etc/ssh/ssh_host_*) can indicate a rebuilt or cloned server. - Correlate accepted logins with wtmp/lastlog and the user's shell history.