ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux登录审计实战:last与lastlog排查登录记录

Linux登录审计实战:last与lastlog排查登录记录

服务器出问题之后,第一时间想弄清楚的事情往往只有一个:谁进来过。last和lastlog就是专门干这件事的两个命令,前者按时间线把登录、登出、重启、关机全部列出来,后者按用户维度告诉你每个账号最后一次登录是从哪个 IP、哪个终端、什么时间进来的。两个命令都属于util-linux包,主流发行版默认就装好,不需要额外配置,属于"出问题才想起来、但真到那时候又必须会用"的那类工具。

这篇文章不打算把 man 手册翻译一遍,而是按实际运维场景来组织:先讲清楚wtmp、lastlog、btmp、utmp这几个文件分别装了什么,再分别拆last和lastlog的常用参数和输出字段,然后用一套完整的验证流程走一遍登录审计、异常 IP 定位、从未登录账户盘点这几个高频动作,最后补上日志轮转、记录被清空如何判断、以及一批排查清单。如果你平时要处理服务器安全审计、账户清理、故障回溯,或者只是想在多用户机器上看看谁动过系统,这篇可以直接照着操作。

需要提前说明的是:last和lastlog展示的都是已经落盘的历史记录,它们不是实时监控工具。想看当前在线用户得用who、w、users;想看认证过程的详细日志得用journalctl -u sshd或者auditd。这几个工具是互补关系,不是替代关系,后面会在对应章节说明什么时候该换工具。

1. last 与 lastlog 核心能力速览

先把两个命令的定位摆清楚,避免用错场景。last是"事件流视角",一条登录就是一行,适合回答"昨天晚上八点到十点之间谁登过机器";lastlog是"账户视角",一个用户一行,适合回答"这个账号到底有没有人用过"。

能力项说明
所属软件包util-linux(主流发行版默认安装)
last 数据来源/var/log/wtmp(二进制记录文件)
lastlog 数据来源/var/log/lastlog(按 UID 索引的稀疏文件)
lastb 数据来源/var/log/btmp(登录失败记录)
当前在线用户/var/run/utmp,由 who / w / users 读取
输出排序last 默认时间倒序,lastlog 默认按 UID 升序
是否需要 root读取 wtmp/lastlog 通常需要 root 或相应文件读权限
是否可指定文件last 支持 -f 指定任意 wtmp 格式文件,lastlog 不支持
是否支持按时间过滤last 支持 -s / -p / -t,lastlog 支持 -b / -t(天数)
是否支持按用户过滤两者均支持
适用场景登录审计、账户盘点、故障回溯、合规检查
不适用场景实时监控、命令级操作审计、文件访问审计

这张表里最容易被忽略的是最后两行。很多人以为last能查到"某人登录后执行了什么",实际上它只记录会话的开始和结束,中间干了什么完全看不到。要做命令级审计,得上auditd、sudo日志、bash的PROMPT_COMMAND或者script,这些不在本文范围内。

第二个容易被忽略的是权限。/var/log/wtmp一般是root:utmp 0664,/var/log/lastlog一般是root:root 0644(部分发行版是root:utmp 0664)。普通用户能否读取,取决于发行版和utmp组的成员配置。如果执行命令后输出为空或者报权限错误,先检查文件权限,不要急着怀疑记录丢了。

2. 适用场景与使用边界

2.1 这个工具适合谁

第一类是负责 Linux 服务器日常运维的工程师。机器多人共用、账号轮转频繁、出了问题需要回溯"上一次登录是什么时候",这类场景last和lastlog是成本最低的入口。

第二类是做安全基线检查的人员。合规要求里经常有一条"定期检查长期未登录账户并禁用",lastlog输出的Never logged in就是最直接的判断依据,配合chage -l看密码有效期,一套组合拳能覆盖大部分账户治理需求。

第三类是普通开发者,在多用户共享的开发机、跳板机上,想确认自己的会话记录、排查"为什么我刚登录就被踢掉"这类问题,用last -x看重启和运行级别变化往往能直接定位。

2.2 能解决什么问题

  • 确认某个账户最近一次登录的时间和来源 IP。
  • 统计一段时间内的登录次数,识别异常高频登录。
  • 发现从未登录过的账户,识别可能的默认账户、测试账户、后门账户。
  • 判断系统是否在非预期时间重启过(last -x reboot)。
  • 排查"用户说自己登不上"到底是密码问题还是根本没发起过连接(结合lastb)。
  • 审计su、sudo之外的会话级痕迹,比如某个pts终端对应哪个来源。

2.3 不适合什么场景

last和lastlog都不记录命令。想看"谁删了数据库"这种问题,它们帮不上忙。

它们也不记录文件访问。想看"谁读了 /etc/shadow",得靠auditd。

它们的数据可以被清空。wtmp是普通文件,root 权限下truncate -s 0就没了,攻击者拿到 root 后第一件事往往就是清日志。所以这类记录只能作为"辅助证据",不能作为"唯一证据"。真正的审计要求场景,需要把日志实时转发到独立的日志服务器,或者开启systemd-journald的远程转发,让本地记录被清除也不影响留存。

最后一点,登录记录涉及个人信息。在合规环境下采集、保存、分析这些数据,需要有明确的目的和授权范围,导出结果不要随意传播,测试环境验证完及时清理。

3. 日志文件机制与环境准备

在动手敲命令之前,把几个文件的职责分清楚,后面所有排查都会顺很多。

文件路径记录内容读取命令写入者
/var/log/wtmp成功登录、登出、重启、关机、运行级别变化lastsystemd-update-utmp / login / sshd
/var/log/btmp登录失败记录lastblogin / sshd
/var/log/lastlog每个用户最后一次登录lastloglogin / sshd
/var/run/utmp当前登录会话who / w / userslogin / sshd / systemd
/var/log/secure 或 /var/log/auth.log认证过程详细日志文本工具 / journalctlsshd / sudo / su

这里有个细节值得单独说:wtmp、btmp、lastlog、utmp都是二进制格式,不能用cat、tail、grep直接看。强行cat /var/log/wtmp会输出一堆乱码,grep也匹配不到任何有意义的字符串。这不是文件损坏,是设计如此。要看内容只能用对应的命令,或者用utmpdump转成可读文本。

lastlog还有一个特殊性:它是稀疏文件,文件偏移量直接对应 UID。也就是说 UID 1000 的用户记录写在偏移量 1000 乘以结构体大小的位置,UID 65534 的记录写在很靠后的位置。这导致ls -lh看到的文件大小可能有几十 MB 甚至上百 MB,但du -h看实际占用可能只有几十 KB。这是正常现象,不是磁盘异常。

3.1 环境确认清单

在开始测试前,建议先确认下面几项:

# 1. 确认命令是否存在及版本 last --version lastlog --version lastb --version # 2. 确认相关文件存在及权限 ls -l /var/log/wtmp /var/log/btmp /var/log/lastlog /var/run/utmp 2>/dev/null # 3. 确认当前身份 id whoami # 4. 确认当前在线用户(对照组) who w

last --version输出的版本号来自 util-linux,不同版本的参数支持有差异。比如--time-format、-s、-p这些参数在较老的发行版(如 CentOS 7 自带的 util-linux 2.23)上可能不存在,执行会报invalid option。遇到这种情况不要怀疑命令写错,先看版本。

如果ls显示/var/log/wtmp不存在,说明系统的wtmp记录功能被关闭了。这在容器环境、精简镜像、部分云主机自定义镜像里很常见。检查方式:

# 查看 systemd 是否屏蔽了 wtmp 更新 systemctl status systemd-update-utmp 2>/dev/null ls -l /usr/lib/systemd/system/systemd-update-utmp* # 检查是否有指向 /dev/null 的软链接 ls -l /var/log/wtmp

如果/var/log/wtmp是一个指向/dev/null的符号链接,那所有登录记录都被丢掉了,last永远是空的。这种情况需要删掉软链接并从备份恢复,或者接受"没有历史记录"这个事实,改用journalctl作为替代数据源。

4. last 命令语法与基础用法

last的基本语法是:

last [选项] [用户名...] [终端...]

不接任何参数时,它读取/var/log/wtmp,按时间从近到远输出全部记录。输出长这样(字段含义下一节展开):

root pts/0 192.168.1.100 Mon Jan 15 09:12 still logged in alice pts/1 10.0.0.23 Mon Jan 15 08:45 - 10:30 (01:45) bob pts/2 172.16.5.8 Sun Jan 14 22:10 - 23:05 (00:55) reboot system boot 5.15.0-91-generi Sun Jan 14 03:00 still running root tty1 Sun Jan 14 02:58 - down (00:02) wtmp begins Sun Jan 14 02:55:33 2024

4.1 常用参数速查

参数作用典型用法
-n 数量只显示最近 N 条last -n 20
-数量简写形式,等价于 -nlast -20
-f 文件指定 wtmp 格式文件last -f /var/log/wtmp.1
-t 时间显示指定时间之前的记录last -t 20240115000000
-s 时间显示指定时间之后的记录(新版本)last -s 20240101
-p 时间显示指定日期的记录(新版本)last -p 20240115
-x显示关机、运行级别变化等系统事件last -x
-i来源显示为 IP 而非主机名last -i
-R不显示主机名或 IP 列last -R
-a主机名显示在最后一列last -a
-F显示完整时间(年月日时分秒)last -F
-w显示完整用户名和域名last -w
--time-format指定时间输出格式last --time-format=iso

-s和-p这两个参数在 util-linux 2.30 之后才加入,CentOS 7 用户大概率用不了,替代方案是-t配合管道过滤,后面会给出写法。

4.2 按时间和数量过滤

最常见的需求是"只看最近 20 条":

# 两种写法等价 last -n 20 last -20

想看某个时间点之前的记录:

# 格式为 YYYYMMDDHHMMSS,可以省略后面的部分 last -t 20240115000000 last -t 20240115

想看某个时间之后的记录,新版本用-s:

last -s 20240101

老版本没有-s时,用-t加管道反选,虽然笨但能用:

# 显示 2024-01-01 之后、2024-01-15 之前的记录 last -t 20240115000000 | awk '$0 ~ /2024/ && $0 !~ /2023/'

更稳妥的做法是先导出到文件,再用awk按日期列过滤,避免不同版本的输出格式差异导致匹配失败。

4.3 按用户和终端过滤

# 只看指定用户 last alice last alice bob # 只看指定终端 last pts/0 last tty1 # 组合使用 last alice pts/0

按终端过滤在排查"某个 pts 会话是谁开的"时特别有用。比如发现pts/3上有可疑操作,last pts/3能直接告诉你这个会话属于谁、从哪来、开了多久。

4.4 查看系统重启和关机记录

这是last最容易被低估的用法。服务器莫名其妙断过一次,想知道是重启了还是被关机了,直接:

last -x reboot last -x shutdown last -x

-x会把reboot、shutdown、runlevel这些系统事件一起列出来。输出里的reboot和shutdown是伪用户名,不对应真实账户,system boot是内核版本。判断依据很简单:有reboot system boot说明系统重启过,前面的会话如果有- down结尾,说明是被正常关机带下去的。

5. last 输出字段解析与实战验证

5.1 逐列拆解

拿一行典型输出:

alice pts/1 10.0.0.23 Mon Jan 15 08:45 - 10:30 (01:45)
列位置内容说明
第 1 列alice登录用户名
第 2 列pts/1终端设备,pts 为伪终端(SSH、图形终端内的 shell)
第 3 列10.0.0.23登录来源,本地登录时为空
第 4 列起Mon Jan 15 08:45登录时间
之后- 10:30登出时间,仍在线的显示 still logged in
括号内(01:45)会话持续时长,格式为 时:分

终端类型需要单独说明:

  • tty1到tty6:本地虚拟控制台,物理机接显示器登录。
  • pts/0、pts/1:伪终端,SSH 登录、screen/tmux内的新会话、图形界面里的终端窗口都会分配 pts。
  • :0:X11 图形会话。
  • reboot、shutdown、runlevel:系统事件伪用户,不是真实账户。

still logged in表示会话还没结束,此时括号里没有时长。- down表示系统在这个会话期间关机了,时长是关机时间减去登录时间。

5.2 验证流程:制造一条记录再查

想确认记录机制是否正常工作,最直接的办法是制造一条可识别的记录再查回来。步骤如下:

第一步,开一个 SSH 会话或者本地终端,记录当前时间和来源。

第二步,用who确认当前会话存在:

who

输出里应该能看到你的用户名、终端号和登录时间。

第三步,用last查自己:

last $(whoami) -n 5

正常情况下,第一行就是当前会话,状态为still logged in,来源字段与你实际的 IP 或主机名一致。

第四步,退出该会话,再次登录后查询,状态应该变成带时长的时间区间:

last $(whoami) -n 5

如果第一次查有记录、退出再查记录消失了,说明wtmp写入有问题,回到第 3 节检查文件状态。

5.3 用 -F 看完整时间

默认输出省略了年份,跨年查询时容易误判。-F参数会输出完整时间:

last -F -n 10

输出变成:

alice pts/1 10.0.0.23 Mon Jan 15 08:45:12 2024 - Mon Jan 15 10:30:44 2024 (01:45)

做日志比对、工单记录、跨年审计时,建议固定带上-F,避免"1 月 15 日"到底是今年还是去年这种歧义。

5.4 用 -i 避免 DNS 反解干扰

last默认会尝试把来源 IP 反解成主机名。这个行为在两种情况下会拖慢命令:DNS 服务器响应慢,或者内网没有配置反向解析。表现是命令卡住几秒甚至十几秒才出结果。

解决方法是加-i:

last -i -n 20

-i强制显示 IP,不做反解,既快又准确。做审计时也建议固定用-i,因为主机名可能被篡改或者存在同名冲突,IP 更可靠。

5.5 统计登录次数

last本身不带统计功能,但输出格式稳定,用管道处理很方便:

# 统计每个用户成功登录次数,排除系统伪用户 last -i | grep -v -E '^(reboot|shutdown|runlevel|wtmp)' | awk '{print $1}' | sort | uniq -c | sort -rn

这条命令的每一步都有明确目的:grep -v去掉系统事件和结尾行,awk '{print $1}'取第一列用户名,sort | uniq -c计数,最后sort -rn按次数倒序。输出可以直接作为账户活跃度排名的依据。

如果想统计某个 IP 的登录次数,把$1换成$3即可:

last -i | grep -v -E '^(reboot|shutdown|runlevel|wtmp)' | awk '{print $3}' | sort | uniq -c | sort -rn | head -20

这条在识别暴力破解的内网跳板、异常来源时非常有用。

6. lastlog 命令用法与账户盘点

lastlog和last的数据源完全不同,它是按 UID 索引的,所以输出是"一个用户一行",默认按 UID 升序排列,包含系统账户。

6.1 基础用法

不接参数时输出全部账户:

lastlog

输出格式:

Username Port From Latest root pts/0 192.168.1.100 Mon Jan 15 09:12:33 +0800 2024 bin **Never logged in** daemon **Never logged in** alice pts/1 10.0.0.23 Mon Jan 15 08:45:12 +0800 2024

字段含义:

字段说明
Username账户名
Port最后一次登录使用的终端,从未登录则为空
From最后一次登录的来源 IP 或主机名
Latest最后一次登录时间,从未登录显示Never logged in

6.2 按用户过滤

lastlog -u alice lastlog -u alice,bob

排查单个账户时这是最快的方式,比全量输出再 grep 更干净。

6.3 按时间窗口过滤

lastlog的时间过滤是按天数算的,不是日期字符串:

# 显示最近 7 天内登录过的账户 lastlog -t 7 # 显示 30 天之前登录过的账户(含从未登录) lastlog -b 30

这两个参数组合起来可以做"僵尸账户盘点":-b 90找出 90 天没登录的,再加上grep "Never logged in"找出从来没登录过的。

# 超过 90 天未登录的账户 lastlog -b 90 # 从未登录过的账户 lastlog | grep -i "never logged in"

注意-b和-t在部分老版本上行为略有差异,某些版本-b会把从未登录的账户也包含进来,某些不会。做批量处理前,建议先用小范围数据验证一次输出结构。

6.4 验证 lastlog 是否正常工作

lastlog和wtmp是两套独立记录,有可能一个正常一个异常。验证方法:

# 1. 记录当前时间 date # 2. 查看自己账户的 lastlog 记录 lastlog -u $(whoami) # 3. 新开一个 SSH 会话后再查一次 lastlog -u $(whoami)

第二次查询的时间应该更新为刚才那次登录的时间。如果last有新记录但lastlog没更新,通常是/var/log/lastlog权限问题或者被设成了只读。检查:

ls -l /var/log/lastlog stat /var/log/lastlog

正常情况下所有者是 root,权限 0644 或 0664。如果被改成只读(0444)且所有者不是 root,写入会失败。

6.5 识别需要处理的账户

这一节是lastlog最实用的地方。账户盘点通常分三步:

第一步,找出所有从未登录过的账户:

lastlog | grep -i "never logged in" | awk '{print $1}'

第二步,把这些账户和系统必需账户做对比。bin、daemon、sys、sync、games、man、lp、mail、news、uucp、proxy、www-data、backup、list、irc、gnats、nobody、systemd-network、systemd-resolve这些是发行版自带的服务账户,属于正常情况,不要动。

第三步,剩下的才是需要关注的:人为创建但从未使用的账户、离职人员遗留账户、测试账户、以及最需要警惕的——不在预期清单里的账户。对这些账户用chage -l看密码策略,用id看所属组,确认无误后再决定锁定还是删除:

# 查看账户密码有效期信息 chage -l suspicious_user # 查看账户 UID、GID、所属组 id suspicious_user # 锁定账户(保留账户但禁止登录) usermod -L suspicious_user # 锁定并设置过期日期 usermod -L -e 1 suspicious_user

删除账户前务必确认该账户没有被任何服务进程使用,用ps -ef | grep username和find / -user username检查一遍,避免误删导致服务异常。

7. 登录审计实战与异常排查

7.1 定位异常来源 IP

第一步,统计所有登录来源:

last -i | grep -v -E '^(reboot|shutdown|runlevel|wtmp)' | awk '{print $3}' | sort | uniq -c | sort -rn

第二步,对可疑 IP 反查详细记录:

last -i | grep "203.0.113.45"

第三步,交叉验证认证日志。last只能看到成功登录,如果这个 IP 同时存在大量失败记录,说明在做爆破:

# 查看失败登录记录(需要 root) lastb | grep "203.0.113.45" | head -50 # 统计失败次数最多的来源 lastb | awk '{print $3}' | sort | uniq -c | sort -rn | head -20

lastb读的是/var/log/btmp。如果这个文件不存在或者为空,说明系统没有记录失败登录,需要检查sshd配置里的SyslogFacility和 PAM 配置,或者该文件被清理过。有些发行版出于磁盘考虑默认关闭了btmp写入。

7.2 判断记录是否被清空

这是安全排查里最关键的一步。last输出的最后一行永远是以wtmp begins开头的,它告诉你当前wtmp文件里最早的记录是什么时间。这一行是判断日志是否被清洗的核心依据。

last | tail -1

输出类似:

wtmp begins Sun Jan 14 02:55:33 2024

判断逻辑:

  • 如果wtmp begins的时间比系统安装时间晚很多,且中间没有合理的日志轮转解释,说明记录被清空过。
  • 如果/var/log/wtmp文件大小异常小(比如几 KB),但机器已经运行了几个月,同样可疑。
  • 如果wtmp.1、wtmp.2这些轮转文件存在但不连续,中间有缺口,也要警惕。

检查轮转文件和文件时间:

ls -l /var/log/wtmp* ls -l /var/log/btmp* stat /var/log/wtmp

stat输出的Modify时间是最后一次写入时间,Change是元数据变更时间。如果Change时间远晚于Modify,且你并没有做过权限调整,那可能存在人为干预。

7.3 结合 journalctl 交叉验证

wtmp可以被清空,但journald的日志通常在另一个位置,两者交叉比对能提高判断准确度:

# 查看 sshd 登录成功记录 journalctl -u sshd --since "2024-01-01" --until "2024-01-31" | grep -i "Accepted" # 查看登录失败记录 journalctl -u sshd --since "2024-01-01" | grep -i "Failed password" # 查看 sudo 使用记录 journalctl _COMM=sudo --since "today"

把journalctl里的Accepted publickey for alice from 10.0.0.23和last里的记录对上,时间、用户、IP 三项一致才算验证通过。如果journalctl有记录但last没有,基本可以确认wtmp被清理过。

需要提醒的是,journald默认也可能不持久化(Storage=volatile时日志只存在内存),重启即丢。做审计要求高的环境,应该把Storage设为persistent,并配置远程转发。

7.4 排查"用户说登不上"

这类问题分两种:根本没连上,或者连上了但会话立刻断开。

先看有没有成功记录:

last -i $(whoami) -n 10 last -i | grep "username"

如果没有成功记录,再看失败记录:

lastb | grep "username" | head -20

有失败记录但没成功记录,说明密码或密钥验证失败,方向转向认证配置。

如果成功记录存在但时长只有几秒,说明登录后立即被踢,方向转向 shell 配置、/etc/profile、PAM 的pam_nologin、磁盘配额、或者sshd的MaxSessions限制。这时候last提供的"登录时间 + 持续时长"就是最直接的线索。

8. 日志轮转、归档与长期留存

wtmp和btmp会持续增长,不轮转的话几年下来能到几百 MB。主流发行版通过logrotate管理,配置文件通常在/etc/logrotate.conf或者/etc/logrotate.d/下。

8.1 查看当前轮转配置

# 查看主配置里是否有 wtmp 相关条目 grep -n "wtmp\|btmp\|lastlog" /etc/logrotate.conf # 查看独立的配置文件 ls -l /etc/logrotate.d/ | grep -i "wtmp\|btmp" cat /etc/logrotate.d/wtmp 2>/dev/null

典型配置长这样:

/var/log/wtmp { missingok monthly create 0664 root utmp rotate 1 }

含义是:文件不存在时不报错,按月轮转,新建文件权限 0664、所有者 root、组 utmp,保留 1 份历史。也就是说系统上最多同时存在wtmp和wtmp.1两个文件。

8.2 查询历史轮转文件

last的-f参数可以指定任意 wtmp 格式文件,这是查历史记录的标准方式:

# 查询上一份轮转文件 last -f /var/log/wtmp.1 # 组合查询:当前文件 + 历史文件 last -f /var/log/wtmp last -f /var/log/wtmp.1 # 如果有多份历史文件,按时间顺序处理 for f in /var/log/wtmp.*; do echo "=== $f ===" last -f "$f" -n 5 done

需要说明的是,last -f不会自动去读轮转文件,必须手动指定。所以"为什么last只能看到最近一个月的记录"这个问题的答案,往往就是rotate 1加上按月轮转,历史记录被覆盖了。

8.3 调整留存策略

如果审计要求保留更长时间,需要改轮转配置。修改前先备份原文件:

cp /etc/logrotate.d/wtmp /etc/logrotate.d/wtmp.bak

然后把rotate 1改成rotate 12(保留 12 份),monthly保持不变,这样大约能保留一年。改完用logrotate -d做一次 dry run,确认配置语法没问题:

logrotate -d /etc/logrotate.conf

-d是调试模式,只打印将要执行的动作,不真正执行。输出里能看到每个文件的轮转判断结果,确认无误再去掉-d实际执行。

调大留存份数会占用更多磁盘空间,单个wtmp文件大小取决于系统的登录频率。按经验,中等负载的服务器月均几十 KB 到几百 KB,一年也就几 MB,成本可以接受。但如果你要常年留存,更推荐的做法是定期导出成文本归档,而不是无限增加轮转份数:

# 导出当前 wtmp 为文本,按日期命名归档 last -F -f /var/log/wtmp > /var/backups/login-audit-$(date +%Y%m%d).log # 归档后可以压缩 gzip /var/backups/login-audit-*.log

文本归档的好处是可以直接用grep、awk处理,不受二进制格式和命令版本限制。

8.4 清空日志的正确姿势

测试环境需要重置记录时,直接用>重定向会失败,因为 shell 会尝试以普通文件方式打开,而wtmp可能被进程持有。正确写法是用truncate:

# 清空 wtmp(保留文件本身和权限) truncate -s 0 /var/log/wtmp # 清空 btmp truncate -s 0 /var/log/btmp # 清空 lastlog 前先备份 cp /var/log/lastlog /var/backups/lastlog.bak truncate -s 0 /var/log/lastlog

truncate -s 0只把文件长度设为 0,不改变 inode、权限和所有者,写入进程持有的文件描述符依然有效,后续记录会正常追加。相比之下,rm后重建会导致权限变化,还可能因为进程持有旧 inode 而出现"新记录写不进去"的问题。

清空lastlog后,所有账户的lastlog查询都会显示Never logged in,直到各自重新登录一次。这个操作在生产环境要慎重,清空前必须备份。

9. 常见问题与排查方法

下面这张表覆盖了last和lastlog使用过程中最常见的异常现象。

问题现象可能原因排查方式解决方案
last 输出为空wtmp 不存在或被软链到 /dev/nullls -l /var/log/wtmp恢复文件或改用 journalctl
last 只有 wtmp begins 一行wtmp 被清空last 查看 wtmp begins 时间结合 journalctl 交叉验证
执行 last 报 Permission denied当前用户不在 utmp 组或无读权限ls -l /var/log/wtmp用 root 执行或调整权限
last 卡住十几秒才输出DNS 反解超时对比加 -i 前后耗时固定使用 last -i
last -s 报 invalid optionutil-linux 版本过老last --version改用 -t 加管道过滤
lastlog 全部显示 Never logged inlastlog 被清空或权限异常ls -l /var/log/lastlog备份后重建文件
lastlog 文件很大但 du 很小稀疏文件特性,正常现象ls -lh 与 du -h 对比无需处理
lastb 无输出btmp 不存在或未启用失败记录ls -l /var/log/btmp检查 PAM 和 sshd 配置
时间显示缺年份默认输出格式省略年份加 -F 参数固定使用 last -F
用户名显示不全用户名过长被截断加 -w 参数last -w
记录里有陌生账户可能是后门或遗留账户lastlog -u 用户名 / id 用户名确认后 usermod -L 锁定
某会话时长异常短登录后被 PAM 或 shell 配置踢出查看 /etc/profile、pam_nologin逐项排查登录后脚本

9.1 关于"last 看不到最近的登录"

这是咨询频率最高的问题,原因通常有三个,按概率排序:

第一个是登录方式不写wtmp。某些容器环境、非标准 SSH 实现(比如某些 Web 终端、kubectl exec)不会触发wtmp写入。这类登录在who里也看不到,属于设计限制,无解,只能用应用层日志替代。

第二个是wtmp轮转后没查历史文件。表现是"上周的登录能查到,昨天的查不到",这时候看ls -l /var/log/wtmp*有没有.1文件,有的话用-f指定查询。

第三个是时区问题。last显示的时间是本地时区,但如果服务器时区被改过,或者登录时区和当前时区不一致,时间对比会错位。用timedatectl确认当前时区,用last -F看完整时间戳,避免误判。

9.2 关于"用户名显示成数字"

last输出第一列偶尔会出现纯数字,这是因为该 UID 对应的账户已经被删除,但wtmp里的历史记录还在。last找不到用户名映射时,会退化成显示 UID。

处理方式很简单:这是历史记录的正常表现,不需要修复。如果确实要清理,只能清空wtmp,但那会丢失所有其他记录,不划算。更好的做法是在删除账户前先导出该账户的登录记录归档:

last -F -i 用户名 > /var/backups/user-login-用户名-$(date +%Y%m%d).log

9.3 关于 lastlog 的写入延迟

部分环境下,lastlog的更新不是在登录瞬间完成,而是由 PAM 的pam_lastlog模块在会话建立后写入。如果登录后立刻执行lastlog查询,可能看到的是上一次的记录。这不是 bug,稍等片刻或者重新登录一次即可。做自动化测试时,建议在登录和查询之间留出几秒间隔,避免时序问题。

10. 最佳实践与使用建议

第一,把last -F -i作为默认查询方式。-F消除年份歧义,-i避免 DNS 反解导致的卡顿和主机名伪造风险,两个参数加起来几乎没有副作用,养成习惯能省掉很多解释成本。

第二,审计时永远做交叉验证。last和lastlog只覆盖会话级信息,真正判断一个账户是否异常,至少要同时看lastb的失败记录、journalctl -u sshd的认证日志、sudo的操作日志。单一数据源得出的结论容易误判,尤其是wtmp本身可以被清理这一前提。

第三,给日志留存定一个明确策略。默认的rotate 1加按月轮转,实际上只能回溯一个月左右。有审计要求的场景,要么把rotate调到 12,要么定期导出文本归档,要么配置journald远程转发。三者至少要做一个,不要等到出事才发现记录只剩一周。

第四,账户盘点做成周期性任务。每月跑一次lastlog -b 90和lastlog | grep -i "never logged in",输出结果人工过一遍。这件事单次成本很低,但能及早发现遗留账户、测试账户和异常新增账户。配合chage -l检查密码有效期,基本能覆盖账户治理的主要面。

第五,测试环境的清空操作要留痕。truncate -s 0 /var/log/wtmp这类命令执行前先cp备份,执行后记录时间和操作人。生产环境原则上不做清空,只做归档和轮转。

第六,涉及登录记录的处理要注意合规。这类数据能定位到具体人员的行为和时间,采集、保存、导出、共享都需要有明确授权范围。导出的审计文件不要放在公开可访问的目录,测试完成后及时清理,避免个人信息外泄。

第七,.bash_history和last要分开理解。前者是命令历史,可以被用户自行修改或清空;后者是会话记录,普通用户改不了。两者结合看,才能大致还原一个会话的全貌。但两者都不具备法律意义上的完整性,涉及取证场景应该交给专业流程处理。

最后补一个容易被忽略的组合:last -x加上journalctl --list-boots,能把系统重启记录和内核启动日志对上,排查"机器为什么半夜重启了"这类问题时,比单看任何一个都管用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进