在很多 Linux 运维和应急响应场景里,管理员遇到的第一类问题往往不是"服务为什么挂了",而是"这台机器最近有没有人登录过、谁在什么时间从哪个 IP 登进来的、失败登录是不是突然变多了"。这类问题的答案,绝大多数就藏在三个不起眼的文件里:/var/log/wtmp、/var/log/btmp和/var/log/lastlog。而能把这些二进制日志翻译成人能读的内容的命令,正是last、lastb和lastlog。
很多人对这三个命令的印象停留在"面试会考"的层面,觉得last就是看一眼谁登录过,lastlog就是查一下某个人上次登录时间。实际在排查入侵痕迹、做合规审计、定位异常账号、写自动化巡检脚本时,这三个命令的用法要复杂得多,也更容易踩坑。比如为什么last的输出里会出现reboot这样一条"假用户"记录?为什么lastb明明有日志却提示文件不存在?为什么lastlog在用户量大时执行得特别慢,甚至直接报错?
这篇文章会从"登录审计到底要解决什么问题"出发,把last和lastlog的数据来源、字段含义、参数组合、脚本化用法、日志轮转配置以及常见故障一次性讲清楚。读完之后,你应该能独立完成一次基本的登录行为排查,并且能把这几条命令固化进日常巡检脚本里,而不是每次出事才临时翻手册。
1. 这篇文章真正要解决的问题
先说清楚一件事:last和lastlog不是用来"看日志"的,它们是用来回答特定审计问题的查询工具。区分这一点很重要,因为如果你把它当成cat日志的替代品,就会在参数上反复试错,最后还是没得到想要的结论。
在实际工作中,这几个命令主要覆盖四类需求。第一类是日常运维确认,比如刚给某个同事开了账号,想确认他到底有没有成功登录过,密码是不是已经改掉了;第二类是异常排查,比如怀疑有人用测试账号跑了不该跑的操作,需要还原他的登录时间线和来源地址;第三类是安全审计,包括等保、内部合规检查里经常要求的"账号登录记录留存与可追溯";第四类是自动化巡检,把登录信息采集进监控系统,对异常时段登录、来源 IP 突变、短时间内大量失败登录做告警。
这四类需求对命令的要求完全不同。日常确认只需要一条lastlog -u zhangsan;异常排查需要last配合时间范围筛选和格式化输出;安全审计要求你先搞清楚日志保留多久、轮转策略是什么;自动化巡检则要求输出稳定、可解析,能塞进脚本里被awk、grep二次处理。
所以这篇文章的组织逻辑是:先讲清楚数据从哪来(不理解数据来源,后面所有"查不到"的问题都无从下手),再讲命令怎么用,最后讲怎么把它变成一个可复用的排查流程。如果你只是想快速上手,可以直接跳到第 4、5 节;如果你曾经遇到过"明明登录过但查不到记录"的情况,建议先读第 2 节。
这里给一个明确判断:last和lastlog的价值不在于命令本身有多复杂,而在于它们背后那套 utmp/wtmp/lastlog 的记录机制。掌握机制,你才知道记录什么时候会丢、什么时候会不准,以及为什么有时候必须换用journalctl或审计日志来补充。
2. last 与 lastlog 的底层数据来源与核心概念
2.1 三个日志文件分别记录什么
要理解这三个命令,先要把 Linux 登录记录的文件体系理清楚。Linux 并没有把所有登录信息塞进一个大日志里,而是按用途拆成了几个二进制文件,各自负责不同的记录类型。
| 文件路径 | 记录内容 | 由谁读取 | 典型权限 |
|---|---|---|---|
/var/run/utmp | 当前正在登录的用户会话 | who、w、users | 通常 664,属组 utmp |
/var/log/wtmp | 历史登录、登出、系统启动与关机记录 | last | 通常 664,属组 utmp |
/var/log/btmp | 失败的登录尝试 | lastb | 通常 600,仅 root 可读 |
/var/log/lastlog | 每个用户最后一次登录的信息 | lastlog | 通常 664,属组 utmp |
这几个文件有一个共同特点:它们都是二进制格式,不能直接用cat、tail、vim正常查看。你用cat /var/log/wtmp看到的是一堆乱码,这不是文件损坏,而是设计如此。这种设计的初衷是写入效率高、定长记录便于快速索引,代价就是必须依赖专门的工具来解析。
这里有一个非常容易被误解的点:utmp和wtmp名字很像,但作用完全不同。utmp是"当前状态",谁现在在线;wtmp是"历史流水",谁什么时候登录过、什么时候退出。很多新手会用who去查历史登录,结果只看到当前会话,然后怀疑日志丢了。记住这个区分:查当前用who/w,查历史用last。
2.2 last 与 lastlog 的定位差异
虽然都能查登录信息,但last和lastlog的查询模型完全不一样,这也是选择它们的关键依据。
last是按时间倒序的事件流。它把wtmp里的记录从新到旧一条条列出来,每一条是一个"事件"——一次登录、一次登出、一次重启。你可以按条数限制(-n)、按时间区间过滤(-s/-t),但它天然不适合回答"某个用户最后一次登录是什么时候",因为你需要自己去翻。
lastlog是按用户聚合的快照。它是为每个 UID 维护一条记录,直接告诉你"这个用户最后一次登录的时间、终端和来源"。它天然适合回答"这个账号还活着吗""这个账号多久没用了",但不适合还原时间线,因为它每个用户只保留最后一条。
用一个类比:last像是监控录像的回放,lastlog像是每个员工工位上的"最后打卡记录"。要查某天发生了什么,看录像;要查某个人最近有没有来过,看打卡记录。
理解了这层差异,很多选择困难就消失了。比如做账号清理时,正确做法是lastlog一把梭,找出所有"Never logged in"或者长期未登录的账号;而做入侵溯源时,必须用last拉时间线,配合lastb看爆破痕迹。
2.3 记录是怎么写进去的
登录记录不是内核自动写的,而是由登录程序主动写入。这个过程大致是:用户通过 SSH、本地终端或图形界面登录时,login、sshd、gdm等程序在认证成功后会向utmp和wtmp写入一条登录记录;退出时再写入一条登出记录;系统启动和关机时,由init/systemd写入reboot和shutdown记录。
这个机制带来两个重要推论。第一,如果登录程序本身被绕过或者被替换,记录可能缺失。比如某些非标准方式启动的会话、容器内的进程、直接调用su/sudo的切换,未必都会在wtmp里留下标准记录。第二,记录的可信度依赖于系统完整性。如果攻击者拿到了 root 并且清空了wtmp,last自然什么都查不到,这时候就需要靠远程日志服务器或者auditd来兜底。
所以正确的认知是:last/lastlog是"第一现场",方便快捷,但不能作为唯一证据来源。生产环境里,重要的登录审计一定要做日志外发。
3. 环境准备与前置条件
last、lastb、lastlog这三个命令在绝大多数 Linux 发行版上是预装的,它们属于util-linux软件包。你可以先用下面的命令确认环境。
# 确认命令是否存在及所属软件包 which last lastb lastlog # Debian/Ubuntu 系查询所属包 dpkg -S "$(which last)" # RHEL/CentOS/Rocky 系查询所属包 rpm -qf "$(which last)" # 查看 util-linux 版本 last --version如果命令不存在,安装方式也很直接:
# Debian/Ubuntu sudo apt-get update sudo apt-get install -y util-linux # RHEL/CentOS/Rocky/AlmaLinux sudo dnf install -y util-linux需要提前说明的是,util-linux的具体版本会随发行版而不同,本文不绑定某个具体版本号,所有示例都以通用行为为准。如果你在较新的发行版上发现lastlog的行为和本文描述有出入,很可能是因为部分新版本已经开始用lastlog2逐步替代传统的lastlog实现,遇到这种情况请以man lastlog的实际说明为准。
权限方面需要注意两点。第一,last读取的wtmp通常是 664 且属组为utmp,普通用户一般也能读,所以last通常不需要 root。第二,lastb读取的btmp通常是 600 仅 root 可读,因为失败登录记录里包含大量来源 IP 和尝试的用户名,属于敏感信息,普通用户执行会提示权限不足或者"文件为空"。
# 确认日志文件是否存在及权限 ls -l /var/log/wtmp /var/log/btmp /var/log/lastlog # 典型输出类似 # -rw-rw-r-- 1 root utmp 70656 Jun 10 09:12 /var/log/wtmp # -rw------- 1 root utmp 12288 Jun 10 09:15 /var/log/btmp # -rw-rw-r-- 1 root utmp 292292 Jun 10 09:12 /var/log/lastlog如果你的环境里lastb提示"btmp: No such file or directory",这不是命令坏了,而是这个文件从来没被创建过——只有发生过失败登录,系统才会写入并可能创建这个文件。这一点在第 8 节的排查表里还会再提。
4. last 命令核心用法拆解
4.1 基础输出字段怎么读
先跑一次最朴素的last,把输出结构看明白。
last -n 15典型输出大致是这样:
root pts/0 192.168.10.21 Mon Jun 10 09:12 still logged in ops pts/1 10.20.3.55 Mon Jun 10 08:47 - 09:05 (00:18) root pts/0 192.168.10.21 Mon Jun 10 08:40 - 08:58 (00:18) reboot system boot 5.10.0-28-amd64 Mon Jun 10 08:35 still running root tty1 Sun Jun 9 21:03 - 21:40 (00:37) wtmp begins Sun Jun 2 03:17:44 2024逐列拆开看:
- 第一列是用户名。这里出现的
reboot并不是一个真实账号,而是shutdown/reboot写入的特殊记录,表示一次系统启动。同理你还会看到shutdown、runlevel这类伪用户记录。 - 第二列是终端。
pts/0、pts/1是伪终端,通常对应 SSH 或图形终端里的会话;tty1是本地控制台;:0这类通常对应图形界面登录。 - 第三列是来源。远程登录显示主机名或 IP,本地登录这一列通常为空。
- 第四列是登录时间。
- 第五列开始是登出时间与持续时长。如果会话还在,会显示
still logged in;如果是系统启动记录,会显示still running。 - 最后一行
wtmp begins ...非常关键,它告诉你日志的起点。如果这个时间比你预期的新很多,说明日志被轮转过或者被清理过,历史记录已经不在这个文件里了。
这一行wtmp begins经常被忽略,但它在排查时是判断"查不到到底是没登录还是日志没了"的第一手依据。
4.2 常用参数组合
last的参数不多,但组合起来很实用。下面这几个是日常最常用的,建议直接记住。
# 只看最近 20 条记录 last -n 20 # 显示完整时间(含年份、秒),默认格式会省略年份 last -F -n 20 # 不做主机名反解,直接显示 IP,速度更快也更准确 last -i -n 20 # 不显示来源主机列,输出更紧凑 last -R -n 20 # 宽格式输出,避免长主机名或长用户名被截断 last -w -n 20 # 显示重启、运行级别变更、关机等系统事件 last -x # 只看指定时间之后的记录 last -s "2024-06-09 00:00:00" # 只看指定时间之前的记录 last -t "2024-06-10 00:00:00" # 指定时间区间 last -s "2024-06-09 00:00:00" -t "2024-06-10 00:00:00"这里要重点说一下-i和-F。-i的作用是禁止把 IP 反解成主机名,很多人在生产环境发现last执行慢得离谱,原因就是 DNS 反解超时。加上-i之后输出的是纯 IP,既快又避免了主机名带来的歧义。-F则是把默认省略的年份补全,跨年排查时必须加,否则你分不清是今年还是去年的记录。
-x这个参数容易被低估。它会显示reboot、shutdown、runlevel记录,在排查"服务器是不是被人重启过"时非常有用。正常执行last也会显示reboot,但-x会展示得更完整。
还有一个实用技巧:last默认读/var/log/wtmp,但如果系统做过轮转,历史文件通常叫wtmp.1、wtmp.2.gz之类。轮转后的文件如果是明文,可以直接用-f指定读取:
# 读取轮转后的历史文件 last -f /var/log/wtmp.1 -n 30如果文件被gzip压缩了,last -f是读不了的,需要先解压到临时位置再读。这一点在应急排查中很常见,别指望last能直接读.gz。
4.3 用 awk 做二次加工
last的原始输出是给人看的,列宽对齐,但不适合直接做统计。实际巡检中更常见的做法是用awk把关键字段抽出来。
#!/bin/bash # 文件路径:/usr/local/bin/login-report.sh # 用途:输出最近登录用户统计与来源 IP 分布 set -euo pipefail DAYS="${1:-7}" echo "===== 最近 ${DAYS} 天登录用户 Top 10 =====" last -i -s "-${DAYS}days" 2>/dev/null \ | grep -vE '^(reboot|shutdown|wtmp|btmp|$)' \ | awk '{print $1}' \ | sort \ | uniq -c \ | sort -rn \ | head -n 10 echo echo "===== 最近 ${DAYS} 天登录来源 IP Top 10 =====" last -i -s "-${DAYS}days" 2>/dev/null \ | grep -vE '^(reboot|shutdown|wtmp|btmp|$)' \ | awk '$3 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {print $3}' \ | sort \ | uniq -c \ | sort -rn \ | head -n 10这个脚本有几个细节值得说明。第一,grep -vE过滤掉了reboot、shutdown、wtmp begins这些非用户记录,否则统计结果里会混进一堆伪用户。第二,awk '$3 ~ /^[0-9]+\./'用正则判断第三列是不是 IPv4 地址,这样本地登录(第三列为空)的记录会被自然排除。第三,-s "-${DAYS}days"这种相对时间写法比手写绝对时间方便,last支持这种语法。
运行方式和预期输出:
chmod +x /usr/local/bin/login-report.sh /usr/local/bin/login-report.sh 7预期能看到类似这样的结果:
===== 最近 7 天登录用户 Top 10 ===== 12 root 5 ops 2 deploy ===== 最近 7 天登录来源 IP Top 10 ===== 12 192.168.10.21 5 10.20.3.55 2 10.20.3.60如果输出为空,先确认last本身能不能出结果,再看时间范围是不是设得太窄,最后确认wtmp是否被轮转或清理过。
5. lastlog 命令核心用法与完整示例
5.1 基础输出与字段含义
lastlog的输出结构和last完全不同,它是每个用户一行。
lastlog典型输出:
Username Port From Latest root pts/0 192.168.10.21 Mon Jun 10 09:12:03 +0800 2024 ops pts/1 10.20.3.55 Mon Jun 10 08:47:11 +0800 2024 deploy **Never logged in** nginx **Never logged in**四个字段分别是用户名、终端、来源、最后登录时间。需要注意两个细节:Never logged in表示这个账号自创建以来从未登录过;而某些系统账号(比如bin、daemon、sync)通常也显示为从未登录,这是正常现象,不代表异常。
lastlog最实用的价值在于账号清理。系统运行几年之后,往往会残留大量已离职人员的账号、测试账号、临时开通的账号。用lastlog -t可以快速筛出长期未登录的账号:
# 只显示最近 30 天内登录过的用户 lastlog -t 30 # 显示 90 天之前登录过的用户(即长期不活跃) lastlog -b 90 # 查看指定用户 lastlog -u deploy-t和-b是一对:-t N是"最近 N 天内",-b N是"N 天之前"。做账号审计时,lastlog -b 90的输出就是一份很直接的候选清理清单。
5.2 批量导出未登录账号
下面这个脚本把"从未登录"和"长期未登录"的账号整理成表格,便于交给负责人确认。
#!/bin/bash # 文件路径:/usr/local/bin/audit-lastlog.sh # 用途:导出从未登录及长期未登录的普通用户清单 set -euo pipefail IDLE_DAYS="${1:-90}" OUT_FILE="/tmp/lastlog-audit-$(date +%Y%m%d).txt" # 取出 UID >= 1000 的普通用户(不同发行版阈值可能不同,请按实际调整) mapfile -t USERS < <(awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd) { echo "审计时间: $(date '+%F %T')" echo "闲置判定阈值: ${IDLE_DAYS} 天" echo printf "%-20s %-10s %-20s %s\n" "USERNAME" "PORT" "FROM" "LATEST" echo "------------------------------------------------------------" for u in "${USERS[@]}"; do lastlog -u "$u" | tail -n 1 done } | tee "$OUT_FILE" echo echo "结果已保存到: $OUT_FILE"运行方式:
chmod +x /usr/local/bin/audit-lastlog.sh sudo /usr/local/bin/audit-lastlog.sh 90输出示例:
审计时间: 2024-06-10 10:22:31 闲置判定阈值: 90 天 USERNAME PORT FROM LATEST ------------------------------------------------------------ ops pts/1 10.20.3.55 Mon Jun 10 08:47:11 +0800 2024 deploy **Never logged in** test01 **Never logged in**这里用lastlog -u逐个查询,虽然比一次lastlog慢,但好处是只输出普通用户,不会把大量系统账号混进来。awk里的 UID 阈值 1000 是常见约定,但有些发行版把普通用户起始 UID 设为 500,脚本里已经注释说明,落地前请按实际环境调整。
5.3 关于 lastlog 的性能问题
当系统用户数量很多(比如几百上千个账号)时,直接执行lastlog可能明显变慢,甚至报错。原因在于lastlog文件是按 UID 索引的定长记录文件,程序会按 UID 顺序扫描,遇到 UID 很大或者存在空洞时就会读很多无效区域。
如果你遇到lastlog卡顿或者报类似 "Couldn't find ..." 的错误,可以按下面的思路处理:
# 查看 lastlog 文件大小,判断是否异常膨胀 ls -lh /var/log/lastlog du -h --apparent-size /var/log/lastlog # 确认系统内最大 UID,帮助判断索引范围 awk -F: '{print $3}' /etc/passwd | sort -n | tail -n 1如果发现lastlog文件大小远超账号数量应有的规模,通常是因为历史上有过超大 UID 的账号被删除后留下的空洞。这种情况下不要随意删除/var/log/lastlog,删除等同于清空所有用户的最后登录记录,会破坏审计链条。更稳妥的处理是确认业务允许后,用truncate或者让系统在可控窗口内重建,并且操作前做好备份。
6. lastb 与失败登录排查
lastb是last针对/var/log/btmp的专用版本,输出格式和last基本一致,但记录的是失败登录。
# 需要 root 权限 sudo lastb -n 20 # 显示完整时间,便于和时间线对齐 sudo lastb -F -n 20 # 统计失败登录来源 IP Top 10 sudo lastb -i 2>/dev/null \ | grep -vE '^(btmp|$)' \ | awk '{print $3}' \ | sort \ | uniq -c \ | sort -rn \ | head -n 10典型输出:
root ssh:notty 203.0.113.45 Mon Jun 10 03:22 - 03:22 (00:00) admin ssh:notty 203.0.113.45 Mon Jun 10 03:22 - 03:22 (00:00) root ssh:notty 198.51.100.7 Sun Jun 9 23:10 - 23:10 (00:00) btmp begins Sun Jun 2 03:17:44 2024这里的ssh:notty表示通过 SSH 发起但没有分配终端,是典型的密码爆破特征。如果你的lastb输出里出现同一个 IP 在很短时间内对多个用户名连续失败,基本可以判定为自动化爆破尝试。
需要提醒的是,lastb能看到的内容取决于 SSH 的配置。如果sshd_config里关闭了UsePAM或者调整了日志行为,部分失败记录可能不会写入btmp。所以在做安全审计时,不能只依赖lastb,还要结合/var/log/secure(RHEL 系)或/var/log/auth.log(Debian 系)以及journalctl -u sshd来交叉验证。
一个实用的组合排查命令:
# 对比失败登录与成功登录,快速定位疑似被攻破的账号 echo "===== 失败登录 Top 用户名 =====" sudo lastb -i 2>/dev/null | awk '$1 != "btmp" {print $1}' | sort | uniq -c | sort -rn | head echo echo "===== 成功登录 Top 用户名 =====" last -i 2>/dev/null | grep -vE '^(reboot|shutdown|wtmp|$)' | awk '{print $1}' | sort | uniq -c | sort -rn | head如果某个用户名在失败榜上排第一,同时在成功榜上也出现,这就值得进一步核查:要么是本人多次输错密码后成功,要么是爆破成功。这时候要拉出这个账号的完整时间线,确认登录来源 IP 是否和日常一致。
7. 日志轮转与数据保留策略
这是最容易被忽视、但出问题时最致命的一环。很多人查不到历史记录,不是命令用错了,而是日志早就被轮转覆盖了。
wtmp和btmp都是定长记录文件,会随着时间不断增长。大多数发行版通过logrotate对它们做定期轮转。典型的配置写在/etc/logrotate.conf里,内容大致类似:
# 文件路径:/etc/logrotate.conf(片段) /var/log/wtmp { missingok monthly create 0664 root utmp minsize 1M rotate 1 } /var/log/btmp { missingok monthly create 0600 root utmp minsize 1M rotate 1 }这段配置的关键信息是rotate 1,意思是只保留一份历史文件。也就是说,wtmp每个月轮转一次,旧文件变成wtmp.1,再下个月轮转时wtmp.1被覆盖。这意味着默认情况下,你可查的登录历史可能只有一两个月。对于需要满足合规留存要求的环境,这个保留周期通常是不够的。
调整策略时要注意权衡。把rotate调大能保留更久,但wtmp增长很快,在登录频繁的跳板机或 CI 构建机上,一个月的记录可能就有几十兆甚至更大。更合理的做法是本地保留适度周期 + 定期外发归档:
#!/bin/bash # 文件路径:/usr/local/bin/archive-login-logs.sh # 用途:每周归档登录日志到指定目录,保留 180 天 set -euo pipefail ARCHIVE_DIR="/data/audit/login-logs" KEEP_DAYS=180 STAMP=$(date +%Y%m%d) mkdir -p "$ARCHIVE_DIR" # 归档当前的 wtmp 与 btmp(复制而非移动,避免影响正在写入的文件) cp -a /var/log/wtmp "${ARCHIVE_DIR}/wtmp.${STAMP}" 2>/dev/null || true cp -a /var/log/btmp "${ARCHIVE_DIR}/btmp.${STAMP}" 2>/dev/null || true # 压缩旧归档 find "$ARCHIVE_DIR" -type f -name 'wtmp.*' ! -name '*.gz' -mtime +1 -exec gzip -f {} \; find "$ARCHIVE_DIR" -type f -name 'btmp.*' ! -name '*.gz' -mtime +1 -exec gzip -f {} \; # 清理超期归档 find "$ARCHIVE_DIR" -type f -name '*.gz' -mtime +"$KEEP_DAYS" -delete echo "[$(date '+%F %T')] archive done, keep ${KEEP_DAYS} days"配合 crontab 使用:
# 文件路径:/etc/cron.d/archive-login-logs # 每周一凌晨 3 点执行 0 3 * * 1 root /usr/local/bin/archive-login-logs.sh >> /var/log/archive-login-logs.log 2>&1这里用cp -a而不是mv,是因为wtmp正在被登录程序写入,直接移动会导致写入句柄指向一个已经不存在的路径,可能造成后续记录丢失。复制一份出来归档是更安全的做法。归档文件本身包含敏感信息(用户名、来源 IP、时间),存放目录务必收紧权限,建议设置为700并限定属主。
如果你需要更长期、更可靠的审计能力,仅靠本地文件是不够的。考虑接入集中式日志系统,把登录事件实时推送到独立的日志服务器,这样即使本机日志被清理,外部仍有留存。
8. 常见问题与排查思路
下面这张表覆盖了实际使用中最高频的几类问题。遇到"查不到""报错""结果不对"时,建议按顺序对照排查。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
last输出只有当前会话,没有历史 | wtmp被轮转或清理,或刚重启过 | 查看输出末尾wtmp begins的时间 | 检查 logrotate 配置,调整保留周期,启用日志外发 |
lastb提示btmp: No such file or directory | 系统从未发生过失败登录,文件未创建 | ls -l /var/log/btmp确认文件是否存在 | 属正常现象;如需监控,可先手动touch并设权限 600 |
lastb输出为空但确实有失败登录 | 当前用户无权限读取btmp | 用sudo lastb重试,检查文件权限 | chmod 600 /var/log/btmp,属主 root,用 sudo 执行 |
last执行非常慢 | 来源 IP 被反解为主机名,DNS 超时 | 加-i参数对比执行速度 | 日常脚本统一加-i,避免反解 |
| 输出中主机名被截断看不全 | 默认列宽限制 | 观察是否被...截断 | 加-w宽格式输出 |
lastlog输出大量Never logged in | 系统账号或长期未使用的普通账号 | 结合/etc/passwd的 UID 范围过滤 | 对普通账号做清理评估,系统账号忽略 |
lastlog执行卡顿或报错 | lastlog文件因超大 UID 空洞而膨胀 | ls -lh /var/log/lastlog看文件大小 | 备份后评估重建,切勿直接删除 |
last -f读取历史文件失败 | 文件被 gzip 压缩 | file /var/log/wtmp.1确认格式 | 先解压到临时目录,再用-f指定 |
| 跨年记录时间分不清 | 默认格式省略年份 | 观察时间列是否只有月日 | 加-F显示完整时间 |
容器内执行last无任何记录 | 容器有独立命名空间,不共享宿主机wtmp | 对比宿主机与容器内输出 | 审计应在宿主机执行,或挂载宿主机的/var/log |
表格里最后一条值得单独说明。在容器化环境里,/var/log/wtmp往往是容器镜像自带的空文件或者干脆不存在,容器内的last自然查不到任何东西。这不是命令的问题,而是隔离机制的正常表现。做登录审计时,应该在宿主机层面执行,或者在容器启动时把宿主机的日志目录以只读方式挂载进去。但要注意,多容器共享同一个wtmp会带来写入竞争,一般不推荐。
另外,如果排查过程中发现wtmp begins的时间点非常"新鲜",比如系统已经运行半年但wtmp只从三天前开始记录,这时候第一反应应该是怀疑日志被清理过,而不是"这三天没人登录"。这种情况下要立刻去核对logrotate的执行记录、/var/log/messages或journalctl里的相关操作痕迹。
9. 最佳实践与工程建议
把命令用对只是第一步,真正让登录审计产生价值的是把它变成一套稳定的日常机制。下面这几条是实践中反复验证过的建议。
第一,区分"日常查询"和"审计取证"两套用法。日常查询追求快,last -n 20 -i就够了;审计取证追求完整和可追溯,必须用-F显示完整时间,必须记录wtmp begins这个边界,必须把原始输出保存下来。两者不要混用,否则要么太慢,要么证据不完整。
第二,脚本里统一加-i和-F。自动化脚本最怕的就是不确定性。-i消除了 DNS 反解带来的耗时波动和结果歧义,-F消除了跨年时的时间歧义。这两个参数加上去,输出的稳定性和可解析性会明显提升。
第三,过滤伪用户记录要形成固定套路。reboot、shutdown、runlevel、wtmp begins、btmp begins这些都不是真实用户,做统计时如果不过滤,结果就不可信。建议在团队内统一一份过滤规则,写进公共脚本函数里复用。
第四,日志保留周期要和合规要求对齐。默认的rotate 1只能撑一两个月,很多合规要求是六个月甚至更久。上线前先确认要求,再决定是调整 logrotate 还是做外部归档。调整时注意磁盘容量,跳板机这类高频登录的机器要单独评估。
第五,敏感信息要控制可见范围。btmp和lastlog里包含用户名和来源 IP,属于个人信息范畴。文件权限保持 600 或 664 且属组受控,归档目录权限收紧,脚本输出不要随手贴到公开渠道。做审计报告时,来源 IP 可以考虑做脱敏。
第六,不要把last/lastlog当作唯一证据源。它们记录的是"登录行为",不记录"登录后做了什么"。真正的安全审计需要多层证据:wtmp/btmp看登录,auth.log/secure看认证细节,auditd看关键文件和高权限操作,history看命令历史(但可被篡改)。多层交叉才能形成可靠结论。
第七,变更前先备份。涉及/var/log/lastlog重建、logrotate 配置调整、日志清理这类操作,一律先备份再动手。清理登录日志在很多合规体系里本身就是需要审批的操作,别在生产环境上直接试。
第八,把巡检结果接入监控。光有脚本不够,要有告警。可以对"非工作时间登录""新出现的来源 IP""单 IP 短时间失败次数超阈值"设置规则,把last和lastb的输出定时采集后做比对。这比事后翻日志有意义得多。
10. 小结与继续深入的方向
回到最开始的问题:last和lastlog到底解决了什么?它们解决的是"把二进制的登录流水翻译成可判断的信息"这件事。last给你时间线,lastlog给你账号活跃度快照,lastb给你失败尝试的痕迹。三个命令加起来,能覆盖大部分日常运维确认和初级安全排查场景。
但也要清楚它们的边界。它们只记录"登录这个动作",不记录登录之后发生了什么;它们依赖本机文件,文件被清理就失去意义;它们的精度受限于登录程序的写入行为,某些非标准会话可能不留痕。理解这些边界,比记住所有参数更重要。
如果要继续深入,有两个方向值得投入。一个方向是补充认证层日志的排查能力,把wtmp/btmp和sshd日志、PAM 日志串起来看,这样能还原出更完整的认证过程。另一个方向是把审计能力做成体系,包括集中式日志采集、登录行为基线、异常检测规则,让"查日志"从被动响应变成主动发现。
最后留一个可以直接落地的建议:花十分钟,在你负责的机器上跑一遍last -F -n 20和sudo lastb -n 20,看看wtmp begins是哪天,看看失败登录里有没有陌生 IP。这个动作成本极低,但往往能提前发现一些平时完全没注意到的情况。命令本身不复杂,难的是养成定期看一眼的习惯。