排查一台服务器“到底谁登过、什么时候登的、有没有人在试密码”,很多人的第一反应是翻/var/log/secure或者journalctl -u sshd。但在 Linux 自带的用户管理工具里,其实躺着一套更轻量、更快、几乎零依赖的登录审计入口:last和lastlog。它们不需要额外装采集器,不需要开审计服务,只要系统还在正常写登录记录,一条命令就能把“最近谁来过”拉成一张表。
这篇文章就围绕这两个命令展开。核心判断先给出来:last和lastlog不是“查一下登录记录”这么简单的两个查询命令,它们背后对应着三个不同职责的系统数据库文件(wtmp、btmp、lastlog),理解这三个文件的边界,比背参数重要得多。很多人用错last,根本原因不是参数记不住,而是压根不知道last读的是wtmp、lastb读的是btmp、lastlog读的是一个按 UID 索引的稀疏文件。
读完之后你能得到什么:一是能准确解读last和lastlog的输出,知道每一列从哪来;二是能在真实运维场景里做一次完整的登录审计,包括从未登录账户排查、失败登录爆破源定位、轮转日志回溯;三是能避开几个经典的坑,比如误删wtmp、lastlog文件莫名膨胀到几个 G、新服务器上last直接报wtmp begins ...之外什么都没有。
下面从最基础的文件职责讲起,一路走到可复用的审计脚本和工程建议。文中所有命令都在标准 Linux 发行版的util-linux包内,不需要额外安装软件,涉及生产环境的操作会单独标注风险。
1. 为什么登录审计总在出事之后才被想起
日常运维里有个很典型的现象:服务器跑得好好的,没人会去看登录记录;等到磁盘告警、账号异常、客户投诉“数据被人改过”,才想起来要查“最近谁登进来了”。这时候打开终端,敲last,看到一屏密密麻麻的输出,反而更懵了——哪一行是异常登录?still logged in是什么意思?为什么有几行显示gone - no logout?
问题的根源在于,登录记录这类数据的特点是“平时没人看,要看时必须看得懂”。它不像 CPU、内存、磁盘那样有监控曲线,它是一堆文本记录,没有被解读之前,价值等于零。而last和lastlog恰好是把这些原始记录翻译成人能看懂的第一层工具。
另一个容易被忽略的点是:登录信息审计不只是安全团队的事。对普通开发者和运维来说,它至少有三个高频用途。第一,确认某台机器上是否有其他人在同时操作,避免“我改的配置被别人覆盖了”这种扯不清的问题。第二,排查账户是否被长期闲置,做账号清理和最小权限收敛。第三,定位失败登录的来源 IP,判断是不是有人在对你做密码尝试。
这里必须强调一个前提:本文所有命令和脚本,都应该在你有合法授权的服务器上执行。登录记录本身包含用户名、来源 IP、终端设备等敏感信息,读取和导出都要遵守所在组织的安全规范。如果是排查安全事件,正确做法是先保留证据(复制日志文件、记录哈希),再进行分析,不要在原始文件上做任何写操作。
所以这篇文章的定位很明确:它不是man last的中文翻译,而是把“登录审计”这件事拆成可执行的步骤,告诉你每一步在读什么文件、判断依据是什么、结论怎么下、坑在哪里。
2. last 与 lastlog 到底在读什么文件
理解这两个命令,必须先理解 Linux 的登录记账机制。系统并不会把“张三在 10 点 23 分从 192.168.1.5 登录 tty1”这句话直接写进日志文件,而是用一种固定长度的二进制结构体(utmp结构)记录每次会话的开始和结束。这些结构体被追加写入三个不同的文件,last系列命令就是把这些二进制结构体读出来,反序列化成可读文本。
2.1 三个数据库文件的职责边界
先把这三个文件的关系理清楚,后面所有命令的行为都能解释得通。
| 文件路径 | 记录内容 | 对应命令 | 典型权限 |
|---|---|---|---|
/var/log/wtmp | 成功登录、登出、系统启动、关机、运行级别切换 | last | 0664 root:utmp |
/var/log/btmp | 失败登录尝试(密码错误、用户不存在等) | lastb | 0600 root:utmp |
/var/log/lastlog | 每个用户最后一次登录的时间、来源、终端 | lastlog | 0664 root:utmp |
再补两个容易混淆的:
/var/run/utmp(部分发行版是/run/utmp):记录当前登录的会话,是who、w、users的数据来源。它是实时的,重启即清空。/var/log/faillog:记录每个用户的失败登录次数和锁定状态,配合faillog命令使用,偏 PAM 账户锁定策略。
这里的关键区别在于:wtmp是事件流,一条登录记一条、一条登出记一条,可以按时间顺序回放;lastlog是快照,每个 UID 只保留最新的一条,覆盖式写入。所以你想知道“这个月有谁登过”,只能用last读wtmp;你想知道“这个账号上一次登录是什么时候”,用lastlog最快,一条命令出结果。
2.2 last 与 lastlog 的差异对比
很多教程把这两个命令混着讲,导致读者以为功能重叠。实际上从使用场景看,它们的差别非常清晰。
| 对比维度 | last | lastlog |
|---|---|---|
| 数据来源 | /var/log/wtmp(默认) | /var/log/lastlog |
| 数据形态 | 事件流水,按时间倒序 | 每用户一条,按 UID 排序 |
| 能查历史 | 能,只要wtmp没被轮转删除 | 不能,只保留最后一次 |
| 能查失败登录 | 不能,需用lastb | 不能,需用faillog |
| 输出行数 | 与登录事件数成正比 | 与系统 UID 数量成正比 |
| 典型用途 | 审计一段时间内的登录活动 | 快速判断账户是否长期未使用 |
一句话总结:last回答“发生过什么”,lastlog回答“最后一次是什么时候”。搞清楚这一点,后面选命令就不会犹豫。
3. 环境准备与前置检查
last、lastb、lastlog都属于util-linux软件包,几乎所有主流发行版默认安装。不需要编译、不需要服务、不需要配置文件,这也是它在应急排查时特别好用的原因——哪怕系统已经被折腾得乱七八糟,这几个命令大概率还能跑。
执行前建议先做几项检查,避免拿到错误的数据还当成结论。
# 1. 确认命令是否存在,以及来自哪个软件包 which last lastb lastlog rpm -qf $(which last) 2>/dev/null || dpkg -S $(which last) 2>/dev/null # 2. 确认三个记录文件是否存在及权限 ls -l /var/log/wtmp /var/log/btmp /var/log/lastlog # 3. 确认当前用户能否读取(普通用户通常可读 wtmp、lastlog,读不了 btmp) id关于权限,这里有个细节值得单独说。/var/log/btmp的权限通常是0600 root:utmp,也就是只有 root 能读。这不是配置失误,而是刻意的设计——失败登录记录里包含大量外部 IP 和被尝试的用户名,暴露出去等于告诉攻击者哪些账号存在。所以普通用户执行lastb会遇到权限错误,这是预期行为,不是环境坏了。
如果检查发现/var/log/wtmp不存在,先别急着touch一个。空文件和老系统格式不兼容时,last会输出wtmp begins ...之后什么都没有,或者直接报格式错误。正确做法是确认logrotate配置里是否有针对wtmp的轮转规则,以及是不是有人手动删过。
# 查看 logrotate 主配置中与 wtmp/btmp 相关的段落 grep -A 6 -E "^/var/log/(wtmp|btmp)" /etc/logrotate.conf 2>/dev/null如果这条命令没有任何输出,说明你的发行版把轮转规则放在了/etc/logrotate.d/下,或者压根没有配置wtmp轮转(部分精简镜像会这样)。没有轮转意味着wtmp会无限增长,长期运行下来几 MB 到几十 MB 都正常,极端情况可能更大,这一点在后面的排查章节会展开。
4. last 命令核心用法拆解
last的默认行为很简单:读wtmp,按时间倒序输出所有登录记录。但默认输出往往信息量过大,实际使用中几乎总要配合参数。
4.1 基本输出格式解读
先看一条典型的输出,把每一列的含义对齐。
last -n 8预期输出大致如下(具体内容以你的机器为准):
root pts/0 192.168.1.20 Mon Mar 10 09:12 still logged in deploy pts/1 10.0.3.55 Mon Mar 10 08:47 - 09:05 (00:18) root tty1 Sun Mar 9 22:31 - 23:02 (00:31) reboot system boot 5.10.0-28-amd64 Sun Mar 9 22:28 - 09:15 (10:47) deploy pts/0 10.0.3.55 Sun Mar 9 20:03 - 20:41 (00:38) root pts/0 192.168.1.20 Sun Mar 9 15:22 - 15:40 (00:18) wtmp begins Sun Mar 2 03:11:47 2025逐列解释:
- 第 1 列:用户名。特殊值有
reboot(系统启动)、shutdown(关机)、runlevel(运行级别变化)。 - 第 2 列:终端名。
pts/N是伪终端(SSH、图形终端里的终端窗口),ttyN是本地虚拟控制台,:0通常是图形界面登录。 - 第 3 列:来源。远程登录显示 IP 或主机名,本地
tty登录这一列为空。 - 第 4 到 6 列:登录时间与登出时间。
- 括号内:会话持续时长。
- 最后一行
wtmp begins ...:这是wtmp文件中最早的一条记录时间,也是你能回溯的时间下限。
这里有个高频误读点:still logged in表示会话还没有正常结束记录。它可能真的还在线,也可能是会话异常断开(比如网络中断、客户端崩溃)导致系统没来得及写登出记录。不能仅凭still logged in就断定这个用户此刻在线,要交叉验证who或w。这个区别在安全排查里非常重要,后面会专门讲。
4.2 常用参数组合
last的参数不多,但组合起来覆盖了大多数场景。
# 只看最近 10 条 last -n 10 # 显示完整时间(含年份和秒),审计场景强烈建议加 last -F -n 20 # 自定义时间格式 last --time-format iso -n 20 # 显示完整用户名和域名,避免被截断 last -w -n 20 # 显示来源 IP 而不是反解后的主机名 last -i -n 20 # 只看某个用户的登录历史 last deploy # 只看某个终端的历史 last pts/0 # 按时间范围过滤(较新版本支持) last --since "2025-03-01" --until "2025-03-10"关于-i这个参数,值得多说一句。默认情况下,last会尝试把来源 IP 反解成主机名,反解过程依赖 DNS,如果 DNS 慢或者不通,命令会卡住好几秒甚至更久。在应急排查时这种卡顿很要命。所以实际使用中推荐始终带上-i,直接显示 IP,既快又准确。此外,反解出来的主机名还可能被伪造的 PTR 记录误导,-i也顺带规避了这个风险。
-F参数同样建议形成习惯。默认输出省略年份,跨年数据放在一起看会直接错乱——同一条Mar 10 09:12,你根本不知道是今年还是去年。审计场景下时间必须精确到秒,-F是必须的。
4.3 last -x 与 last reboot
-x参数会额外显示系统级事件,包括关机、运行级别切换、runlevel记录。这个参数在分析“服务器在某个时间点为什么失联”时特别有用。
# 查看系统启动记录 last reboot -F -n 10 # 查看关机记录 last shutdown -F -n 10 # 查看全部事件(含运行级别切换) last -x -F -n 30last reboot的输出里,最后一条如果是still running,说明系统自那之后没有再重启过。这个信息可以用来快速确认机器是否发生过意外重启——如果运维说“没重启过”,但last reboot显示昨天有一次,那就值得追问了。
一个实战经验:判断服务器是否被异常重启,last reboot比uptime更可靠。uptime只告诉你当前运行了多久,last reboot会告诉你每一次重启的准确时间点,能直接对应到变更窗口或告警时间。
5. lastlog 命令核心用法
lastlog的输出结构和last完全不同,它按 UID 顺序列出系统上所有用户(包括系统账户),每行显示用户名、终端、来源、最后一次登录时间。
lastlog | head -n 12预期输出大致如下:
Username Port From Latest root pts/0 192.168.1.20 Mon Mar 10 09:12:44 +0800 2025 daemon **Never logged in** bin **Never logged in** sys **Never logged in** sync **Never logged in** games **Never logged in** man **Never logged in** deploy pts/1 10.0.3.55 Mon Mar 10 08:47:02 +0800 2025这个输出有两个明显特征。第一,系统账户大量显示**Never logged in**,这是正常的,daemon、bin、sync这些账号本来就不允许交互登录。第二,普通用户的显示是“最后一次”而不是“所有记录”,所以lastlog的行数和wtmp大小无关,只和系统的 UID 数量(包括 LDAP/SSSD 拉进来的用户)有关。
常用用法:
# 查看指定用户 lastlog -u deploy # 只显示 UID 范围内的用户 lastlog -u 1000-1100 # 只显示最近 N 天内登录过的用户(老版本写法) lastlog -t 7 # 只显示超过 N 天未登录的用户 lastlog -b 30 # 清空某个用户的 lastlog 记录(谨慎使用) lastlog -C -u testuser关于-t和-b,不同版本的util-linux行为有差异,新版本更推荐--since/--until配合日期使用。如果你的系统上-t 7报错或者行为不符合预期,直接man lastlog看你这一版的参数说明,不要照搬网上的写法。这也是本文不写死版本号的原因——参数在小版本之间确实会变。
关于lastlog -C,这里必须给出明确提醒:它会清空指定用户的最后登录记录,属于写操作。只应该在你有明确授权、且确实需要重置记录的场景下使用(比如账号交接后清空痕迹)。生产环境上随手执行,等于破坏了审计证据。
6. 完整实战:一次登录审计的完整流程
前面讲了单个命令,现在把场景串起来。假设你接到一个任务:某台对外提供服务的 Linux 主机,需要确认最近一周的登录情况,找出长期未使用的可登录账户,并确认是否存在明显的密码尝试行为。
6.1 场景定义与执行顺序
这类任务有固定的执行顺序,顺序错了会导致结论不可靠。推荐按下面四步走:
- 确认数据完整性和时间范围(
wtmp起点、轮转情况)。 - 拉取成功登录记录,按用户和来源聚合。
- 拉取从未登录/长期未登录的可交互账户清单。
- 拉取失败登录记录,按来源 IP 聚合排序。
先做第 1 步的原因很实际:如果wtmp昨天刚被轮转清空,你后面所有“最近一周”的分析都是无源之水。先确认数据边界,再下结论,这是审计的基本纪律。
6.2 分步执行命令
第一步:确认数据边界
# 看 wtmp 最早记录时间,同时确认文件大小和权限 last -F -n 1 | tail -n 1 ls -lh /var/log/wtmp /var/log/btmp /var/log/lastlog # 如果存在轮转文件,一并纳入分析范围 ls -l /var/log/wtmp* 2>/dev/null ls -l /var/log/btmp* 2>/dev/null如果输出里能看到/var/log/wtmp-20250301这类轮转文件,说明历史数据被切分了。last支持用-f指定文件,可以把轮转文件逐个读一遍。
# 读取轮转后的 wtmp 文件 last -F -f /var/log/wtmp-20250301 | head -n 20第二步:成功登录按来源聚合
# 提取最近登录记录中的来源列,统计出现次数最高的前 10 个 last -i -F -n 200 | awk 'NF>=4 && $3 ~ /^[0-9]+\./ {print $3}' | sort | uniq -c | sort -rn | head -n 10这段管道做了三件事:awk过滤出第三列是 IP 格式的行(剔除了reboot、tty本地登录这类没有来源的行),sort | uniq -c做计数,最后按次数倒排。输出能直接告诉你哪些 IP 登录最频繁。如果某个陌生公网 IP 出现次数异常高,就是需要重点确认的对象。
第三步:找出可登录但从未登录的账户
# 先列出所有允许交互登录的账户(shell 为 bash/sh/zsh 等) awk -F: '$7 ~ /\/(bash|sh|zsh|ksh)$/ {print $1}' /etc/passwd > /tmp/interactive_users.txt # 再列出 lastlog 中从未登录的账户 lastlog | grep "Never logged in" | awk '{print $1}' > /tmp/never_users.txt # 求交集 comm -12 <(sort /tmp/interactive_users.txt) <(sort /tmp/never_users.txt)这个交集就是“允许登录但从来没登录过”的账户。这类账户在账号治理里属于典型的高风险项——存在、有 shell、但没人用,一旦凭证泄露就是现成的入口。注意comm要求两个输入都已排序,所以命令里用了进程替换加sort。
第四步:失败登录按来源聚合
# 需要 root 权限 lastb -i -F -n 500 | awk 'NF>=4 && $3 ~ /^[0-9]+\./ {print $3}' | sort | uniq -c | sort -rn | head -n 10如果lastb输出为空或者报权限错误,先确认是不是 root 执行的,再看/var/log/btmp是否存在。有些发行版的 sshd 配置里MaxAuthTries太小,失败记录会被 PAM 层提前拦截,导致btmp里记录不全,这种情况要看journalctl -u sshd补齐。
6.3 脚本化:把审计流程固化成可复用脚本
一次性的命令敲完就忘,下次还得重来。把上面四步封装成一个脚本,才是可持续的做法。
#!/usr/bin/env bash # 文件路径:/usr/local/sbin/login-audit.sh # 用途:登录信息审计,只读操作,不修改任何系统文件 # 使用:sudo /usr/local/sbin/login-audit.sh set -uo pipefail WTMP_FILE="/var/log/wtmp" BTPM_FILE="/var/log/btmp" REPORT_DIR="/var/log/login-audit" TS="$(date +%Y%m%d-%H%M%S)" REPORT="${REPORT_DIR}/audit-${TS}.txt" mkdir -p "${REPORT_DIR}" { echo "==================================================" echo "登录审计报告 生成时间: $(date '+%F %T')" echo "主机: $(hostname) 内核: $(uname -r)" echo "==================================================" echo echo "---- [1] 数据边界 ----" if [ -f "${WTMP_FILE}" ]; then last -F -n 1 -f "${WTMP_FILE}" | tail -n 1 ls -lh "${WTMP_FILE}" else echo "警告: ${WTMP_FILE} 不存在,无法分析成功登录记录" fi echo echo "---- [2] 最近 20 条登录记录 ----" last -i -F -n 20 || echo "无法读取 wtmp" echo echo "---- [3] 登录来源 IP 频次 TOP10 ----" last -i -F -n 300 \ | awk 'NF>=4 && $3 ~ /^[0-9]+\./ {print $3}' \ | sort | uniq -c | sort -rn | head -n 10 echo echo "---- [4] 可交互登录但从未登录过的账户 ----" awk -F: '$7 ~ /\/(bash|sh|zsh|ksh)$/ {print $1}' /etc/passwd | sort > /tmp/.ia_interactive lastlog 2>/dev/null | grep "Never logged in" | awk '{print $1}' | sort > /tmp/.ia_never comm -12 /tmp/.ia_interactive /tmp/.ia_never || true echo echo "---- [5] 失败登录来源 IP 频次 TOP10 ----" if [ -f "${BTPM_FILE}" ] && [ -r "${BTPM_FILE}" ]; then lastb -i -F -n 500 2>/dev/null \ | awk 'NF>=4 && $3 ~ /^[0-9]+\./ {print $3}' \ | sort | uniq -c | sort -rn | head -n 10 else echo "跳过: ${BTPM_FILE} 不存在或当前用户无读取权限(需 root)" fi echo echo "==================================================" echo "报告结束" } | tee "${REPORT}" echo echo "报告已保存至: ${REPORT}"这个脚本有几个设计考虑值得说明。第一,全程只读,tee把结果同时打到终端和文件,不做任何删除或清空动作。第二,所有可能失败的命令都加了|| true或2>/dev/null,避免因为某一段没数据导致整个脚本中断——审计脚本最怕的是中途退出,只出一半报告比不出更坑。第三,报告文件名带时间戳,重复执行不会覆盖历史结果,方便横向对比。
运行方式:
sudo chmod 750 /usr/local/sbin/login-audit.sh sudo /usr/local/sbin/login-audit.sh7. 运行结果验证与结论判断
脚本跑完之后,重点不是看输出有多长,而是看几个关键判断点。下面这张表是实际审计时最常用的判断依据。
| 观察到的现象 | 通常意味着什么 | 下一步动作 |
|---|---|---|
| 大量陌生公网 IP 出现在成功登录记录里 | 可能存在凭证泄露或共享账号 | 核对业务需求,确认账号归属,必要时改密并限制来源 |
lastb中某个 IP 失败次数远高于其他 | 存在密码尝试行为 | 结合防火墙/安全组做访问控制,检查该账号是否已被锁定 |
可交互账户中出现Never logged in | 闲置账号,属于收敛对象 | 确认归属后禁用 shell 或锁定账户 |
last显示gone - no logout | 会话异常中断,未写登出记录 | 结合who/journalctl判断是否仍有残留进程 |
wtmp begins时间就是今天 | wtmp刚被轮转或清空 | 往前找轮转文件,否则历史分析无依据 |
still logged in但who里没有 | 会话已断开但记录未更新 | 属于正常现象,不要误判为在线 |
关于最后两种情况,需要展开讲一下,因为这是最容易得出错误结论的地方。
gone - no logout的含义是:wtmp里记到了这次登录的开始时间,但对应的登出记录没有写进去。常见原因是客户端直接断网、SSH 连接被 kill、系统负载过高导致会话进程异常退出。它不等于“有人还在操作”,也不等于“被入侵”。正确的做法是查who看当前是否有该会话,再查ps -ef看有没有残留的 shell 进程。
still logged in同理。系统认为这个会话还没结束,但实际连接可能早就断了。所以判断“当前谁在线”,永远以who、w为准,last只能作为辅助。这个边界如果搞混,很容易在排查时得出“有人在偷偷登录”的错误结论,反而制造混乱。
至于wtmp begins显示的日期离今天很近,也不一定是异常。如果配置了按月轮转且只保留一份,月初看到wtmp begins是 1 号之后的时间是完全正常的。判断依据应该是轮转配置,而不是日期本身的新旧。
8. 常见问题与排查表
实际使用中踩的坑基本集中在权限、文件、格式、参数四类。下面这张表覆盖了最常见的情况。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
last: /var/log/wtmp: No such file or directory | wtmp被手动删除或从未生成 | ls -l /var/log/wtmp | 不要手动touch,检查 logrotate 配置,重启一次相关服务或等待系统重新写入 |
lastb: /var/log/btmp: Permission denied | btmp权限为0600 root:utmp | ls -l /var/log/btmp | 用sudo执行,不要为了图方便改成 644 |
lastlog输出为空或只有表头 | lastlog文件被清空,或系统使用 SSSD 且记录在别处 | ls -lh /var/log/lastlog | 确认是否有 PAM 模块接管,检查pam_lastlog配置 |
ls -l /var/log/lastlog显示大小几个 G | 稀疏文件,按 UID 偏移写入,大 UID 会撑大显示大小 | du -h /var/log/lastlog对比实际占用 | 属正常现象,无需处理;若du也很大才需要关注 |
last执行后卡住几秒 | 反解来源 IP 的主机名时 DNS 超时 | 加-i试试是否变快 | 养成加-i的习惯,或检查 DNS 配置 |
last时间没有年份,跨年数据混乱 | 默认时间格式省略年份 | 观察输出 | 加-F或--time-format iso |
last --since报参数错误 | 当前util-linux版本不支持 | last --help查看参数列表 | 改用-n结合awk过滤,或升级util-linux |
wtmp持续增大占用磁盘 | 未配置轮转 | grep wtmp /etc/logrotate.conf | 补充轮转规则,参考下一节配置 |
| 重启记录缺失 | 系统强制断电,未走正常关机流程 | last -x对比journalctl -b | 属预期行为,wtmp无法记录硬断电 |
lastlog中大量系统账户显示Never logged in | 系统账户本就不允许登录 | 检查/etc/passwd中的 shell | 无需处理,只关注 shell 为bash/sh的账户 |
其中“稀疏文件”这一条特别值得展开。/var/log/lastlog的存储结构是“以 UID 乘以结构体大小作为文件偏移量”,每个用户一条记录。这意味着如果系统上存在 UID 为 1000000 的用户(常见于 LDAP 或容器环境映射),文件逻辑大小就会达到1000000 × 结构体大小,ls显示出来可能是几十 GB,但实际占用磁盘可能只有几十 KB。看到这个现象不要慌,更不要删文件,用du -h确认实际占用即可。真正需要处理的是 UID 规划不合理的问题,而不是文件本身。
另一个常见的误区是“清空wtmp就能释放空间”。确实能,但代价是丢失全部登录历史。正确做法是配置 logrotate,让系统按周期轮转并保留合理份数,既控制体积又保留审计窗口。
9. 最佳实践与工程建议
聊完命令和排错,最后说说怎么把这件事做扎实。登录审计的价值不在于“会敲命令”,而在于形成一套稳定的、可重复的、有据可查的流程。
9.1 日志轮转配置要显式确认
很多发行版的默认配置里,wtmp和btmp的轮转规则是写在/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 rotate 1 }注意rotate 1表示只保留一份历史文件。对普通业务机够用,但如果你所在的环境有合规要求(比如需要保留 180 天登录记录),这个配置明显不够。调整时不要直接改主文件,更推荐在/etc/logrotate.d/下新建独立配置,避免系统升级时被覆盖。
# 文件路径:/etc/logrotate.d/login-audit /var/log/wtmp { missingok monthly create 0664 root utmp rotate 12 compress delaycompress } /var/log/btmp { missingok monthly create 0600 root utmp rotate 12 compress delaycompress }rotate 12按月轮转即保留一年,compress配合delaycompress保证最新一份不压缩、方便直接用last -f读取。改完之后用logrotate -d /etc/logrotate.d/login-audit做一次 dry-run,确认没有语法问题再让它生效。
这里要提醒一句:不要用cat /dev/null > /var/log/wtmp这种“清空”手法。它确实能让文件瞬间变空,但会同时抹掉全部审计证据,而且没有轮转归档,出事后什么都查不到。生产环境上做这类操作前,务必确认你有授权、有备份、有回滚方案。
9.2 审计结论要交叉验证
单一数据源永远不够。last说某人在线,who说没有,这时候要信who。lastb显示大量失败登录,但journalctl -u sshd里没有对应记录,说明失败可能来自本地su或sudo而不是 SSH。
推荐的交叉验证组合:
# 当前在线会话,以这个为准 who -a # 最近的系统级事件 last -x -F -n 20 # SSH 服务的详细记录(如果使用 systemd-journald) journalctl -u sshd --since "7 days ago" --no-pager | tail -n 50 # 当前进程中的登录 shell ps -ef | grep -E "sshd:.*@|login" | grep -v grep把这几个来源对照着看,结论的可信度会高很多。只看一个来源就下判断,是审计里最容易犯的错误。
9.3 账号治理要形成闭环
登录审计的最终目的不是“查出来一堆记录”,而是推动账号治理。具体来说有三个动作可以固化下来。
第一,定期清理Never logged in的可交互账户。做法不是直接删用户(会留下孤儿文件),而是先usermod -L锁定,观察一个周期确认无人使用后再userdel。删除前记得确认该用户是否有定时任务、是否有正在运行的进程。
第二,限制登录来源。如果业务允许,在 SSH 配置或防火墙层面收敛来源网段,从源头上减少异常登录的可能性。这一步比事后查日志有效得多。
第三,把审计脚本挂到定时任务里,每周生成一份报告存档。这样即使wtmp被轮转,你手里还有历史快照。
# 文件路径:/etc/cron.d/login-audit # 每周一凌晨 3 点执行登录审计,输出保存到 /var/log/login-audit/ 0 3 * * 1 root /usr/local/sbin/login-audit.sh >/dev/null 2>&19.4 权限与合规边界
最后必须强调权限边界。/var/log/btmp保持0600 root不是保守,而是必要。它记录了所有失败登录尝试的用户名和来源,泄露出去等于给攻击者提供了一份“哪些账号存在”的字典。同理,审计报告文件本身也应限制权限,建议chmod 640并归属 root。
如果需要把审计数据交付给其他团队,优先交付聚合后的统计结果(频次、时间段分布),而不是原始登录记录。原始记录包含用户名和 IP,属于敏感信息,交付前要确认对方有对应的授权和数据使用规范。
10. 几个需要提前说清的边界
写到这里,把几个容易被误解的边界集中澄清一下,避免读者在实际使用中走弯路。
边界一:last不是万能的审计工具。它只记录通过 PAM 会话机制建立的登录。如果攻击者通过其他方式获得执行权限(比如 Web 应用漏洞直接反弹 shell),根本不会在wtmp里留下任何记录。所以登录审计只能覆盖“正常登录路径”,不能替代应用层日志和主机入侵检测。
边界二:记录可以被篡改。wtmp、btmp、lastlog都是普通文件,有 root 权限就能改。它们不是不可篡改的审计日志。对安全要求高的环境,应该把日志实时转发到独立的日志服务器,本地文件只作为快速排查的辅助。
边界三:容器环境里这些命令基本失效。容器共享宿主机内核但没有独立的登录会话,/var/log/wtmp通常是空的或者不存在。在容器里查登录记录没有意义,要查就去宿主机查。
边界四:SSSD/LDAP 用户的lastlog行为可能不同。部分环境下pam_lastlog对远程目录用户不写lastlog,导致这些用户在lastlog里一直显示Never logged in,但实际天天在登录。遇到这种情况,用last交叉验证,不要直接据lastlog下结论。
把这几条边界记住,基本就不会出现“用错工具得出错误结论”的情况了。last和lastlog的价值在于快和轻,在于出问题第一时间能拉出一张表来看;它们的局限也很明确,需要和其他日志源配合使用。
如果你手上正好有一台长期运行的服务器,不妨现在就执行一遍last -F -n 30和lastlog | grep -v "Never logged in",看看输出里有没有你不认识的账号或 IP。这个动作只需要十几秒,但很多时候,问题就是在这十几秒里被发现的。