凌晨两点被电话叫起来,一台对外提供服务的机器 CPU 跑满、监控告警里冒出一个从没见过的账号名,登录来源还是境外地址——做过应急响应的人都懂这种场面。最难受的往往不是故障本身,而是不知道从哪儿下手。Linux 日志分析在这种时刻的价值,就是把“我猜它被入侵了”变成“我能拿出时间、账号、命令、文件四样东西对上号”。这篇内容聊的就是这条链路:Linux 日志到底散落在哪些角落,每条日志的可信度有多高,怎么用几条命令把零散记录拼成一条时间线,以及在时间紧、日志可能已经被动过手脚的情况下,怎么保住能用的证据。适合刚接手安全运维的朋友打底,也适合做了几年、但每次排查还是靠grep瞎撞的同行对一遍流程。下面全是我自己踩过坑之后沉淀下来的做法,命令都能直接抄。
1. 应急响应现场,日志分析到底在解决什么问题
1.1 从“机器不对劲”到“能讲清楚发生了什么”
很多人对日志分析的理解停留在“查一下谁登录了”,这远远不够。应急响应的本质是还原事件,而还原事件需要回答五个问题:什么时候开始的、从哪个入口进来的、进来之后做了什么、动过哪些文件、现在还在不在。这五个问题,每一个都要靠日志去支撑,而不是靠直觉。举个最常见的场景:运维发现某个进程占用异常,top一看是个名字很像系统进程的东西,路径却在/tmp下面。这时候如果你只杀掉进程,事情其实一点没解决——它是怎么被放进来的、有没有留下计划任务、有没有改过 SSH 配置,全都不知道,过两天它还会回来。
日志分析要做的,就是把这些“点”连成“线”。单条日志的说服力很弱,比如一条Failed password,可能只是别人密码输错了;但如果同一来源地址在十分钟内失败两百次,紧接着出现一条Accepted password,然后五分钟内/etc/passwd的修改时间变了,这三条放在一起,性质就完全不同了。我在实际项目里见过太多人拿着一条可疑日志就下结论,结果误判,白白折腾一整晚。
所以真正要建立的意识是:日志不是用来“查关键字”的,是用来“做关联”的。你手上每一条记录都是一个时间戳加一个主体加一个动作,把它们按时间排好、按主体串起来,故事自然就出来了。这也是为什么我更愿意把这项工作叫做“时间线重建”,而不是“日志搜索”。
1.2 日志可信度分层:先看什么、后看什么、什么只能当参考
日志不是平等的。同样一条记录,有的几乎没法伪造,有的一句话就能改。我在现场一般会把日志分成三层来看,先看高可信层,再看中可信层,最后把低可信层只当作线索。
| 层级 | 日志类型 | 可信度说明 | 典型用途 |
|---|---|---|---|
| 高 | 内核日志、审计日志、远程日志服务器上的副本 | 由内核或独立服务产生,普通权限改不动 | 判断进程创建、文件改动、账号变更 |
| 中 | systemd-journald 日志、syslog 主日志 | 需要 root 才能删改,但删改本身不留痕 | 判断服务启停、登录行为 |
| 低 | bash_history、应用自身日志 | 用户可以自行编辑、可以关闭记录 | 只作为线索,不作为定论依据 |
这个分层不是学术分类,是实操里的取舍依据。举个例子,/root/.bash_history里干干净净什么都没有,你不能因此得出“没人执行过命令”的结论——只要设置一下环境变量,历史记录就可以不写;反过来,如果 history 里出现了可疑命令,那基本可以认定有人确实敲过,因为没人会无聊到往里写假命令。审计日志则相反,auditd一旦开启,进程创建、文件写入这类事件是内核层面记录的,攻击者要绕过就得先关服务,而关服务这个动作本身又会被记下来。
把这三层想清楚,你在现场就不会把时间浪费在翻应用日志上,也不会因为 history 是空的就放松警惕。
1.3 一套我常用的五步排查流程
流程这东西,写出来平平无奇,但在半夜三点脑子不清醒的时候,它能救命。我现在基本固定走这么五步,顺序不乱:
- 定型:先确认现象,是告警触发、还是业务异常、还是例行检查发现。这一步决定你后面看哪类日志,别一上来就全量翻。
- 采集:在不改动系统的前提下,把相关日志、进程列表、网络连接、账号列表、计划任务全部导出到一块独立介质,先留底再分析。
- 粗筛:用时间窗口把范围压到小时级甚至分钟级,把明显无关的记录剔掉。
- 关联:把登录、命令、文件、网络四类记录按时间对齐,找出交集。
- 验证:对推断出的结论做反向验证,比如怀疑某文件被写入,就去查文件时间戳、查审计记录、查有没有对应进程。
这里最容易出问题的是第二步。我见过太多次,排查的人一边分析一边动手,kill进程、删文件、重启服务,等反应过来想回头核对,现场已经没了。所以我的习惯是:先导出,再动手;导出之前连命令都尽量少敲。导出的东西至少包括:/var/log整个目录、journalctl全量输出、当前进程树、当前网络连接、crontab和/etc/cron.d内容、/etc/passwd与/etc/shadow的元信息。这些加起来通常也就几百兆,一块 U 盘就能装下,但它决定了你后面能不能复盘。
2. Linux 日志家底盘点:文件在哪、记了什么、能撑多久
2.1 传统 syslog 体系下的核心文件清单
不管发行版怎么变,/var/log下面那几个文件基本是绕不开的。我列一份自己常用的对照表,RHEL 系和 Debian 系会有点差异,看的时候别混。
| 文件路径 | 记录内容 | 主要出现在 |
|---|---|---|
/var/log/messages | 系统级常规消息,服务启停、内核提示 | RHEL / CentOS 系 |
/var/log/syslog | 同上,范围略宽 | Debian / Ubuntu 系 |
/var/log/secure | 认证相关,登录、sudo、su | RHEL / CentOS 系 |
/var/log/auth.log | 认证相关 | Debian / Ubuntu 系 |
/var/log/cron | 计划任务执行记录 | 多数发行版 |
/var/log/audit/audit.log | 审计子系统记录 | 开启 auditd 的机器 |
/var/log/btmp | 失败登录记录,二进制格式 | 多数发行版 |
/var/log/wtmp | 成功登录与登出,二进制格式 | 多数发行版 |
/var/log/lastlog | 每个账号最后一次登录,二进制格式 | 多数发行版 |
/var/log/boot.log | 本次启动过程记录 | 部分发行版 |
这几个文件里,btmp、wtmp、lastlog是二进制格式,直接cat会看到乱码,得用last、lastb、lastlog这几个命令去读。它们的价值很高——last能看到登录来源、登录时长、从哪个终端进的,lastb能看到失败尝试的来源分布,lastlog能一眼看出哪些账号很久没登录、哪些账号莫名其妙刚登录过。有一次我就是靠lastlog发现一个本该停用的服务账号在凌晨有过登录记录,顺藤摸瓜找到了一条被忽略的入口。
还有一类常被忘掉的是软件包管理日志,/var/log/yum.log或者/var/log/dpkg.log。攻击者落地工具时常会装依赖包,这类日志能告诉你“什么时候装了什么”。它不属于安全日志,但在拼时间线的时候经常能补上关键一格。
2.2 systemd-journald:二进制日志怎么查才不抓瞎
现在主流发行版都用systemd-journald接管了一部分日志,它的好处是结构化、字段全,坏处是默认存在内存里、重启就丢,除非你确认过/var/log/journal目录存在(存在说明开了持久化)。现场第一件事我通常会敲:
ls -ld /var/log/journal 2>/dev/null && echo "持久化已开启" || echo "仅内存,重启即丢"如果只存在内存,那你重启一次机器,之前的日志就没了,这个信息必须在动手前告诉所有人。
查 journal 的常用姿势有这么几个,我按使用频率排:
# 按时间窗口看,最常用 journalctl --since "2024-05-11 00:00:00" --until "2024-05-11 06:00:00" --no-pager # 只看某个服务的 journalctl -u sshd --since today --no-pager # 按优先级过滤,只看错误及以上 journalctl -p err --since "2024-05-11" --no-pager # 输出成 JSON,方便后续脚本处理 journalctl --since "2024-05-11" -o json-pretty --no-pager | head -50 # 看内核消息,排查驱动、OOM、模块加载 journalctl -k --since "2024-05-11" --no-pager-o json-pretty这个参数值得单独说。它输出的每条日志都带完整字段,包括_PID、_UID、_COMM(进程名)、_EXE(可执行文件路径)、_CMDLINE(完整命令行)。排查可疑进程时,_EXE和_CMDLINE这两个字段的信息量远超普通文本日志,因为进程名可以随便改,但_EXE指向的真实路径和启动参数骗不了人。
注意:
journalctl默认会调分页器,脚本里用一定要加--no-pager,否则会卡在less里出不来,半夜排查时这个坑很折磨人。
另外一个细节:journal 的默认保留策略是按磁盘占用比例来的,配置在/etc/systemd/journald.conf里的SystemMaxUse。如果一台机器日志量很大,可能只留一两天。所以现场问清楚“这台机器日志能留多久”,比直接埋头查更重要。
2.3 安全与审计类日志:登录记录、auditd、sudo
安全相关的日志我分成三块看。
第一块是认证日志,也就是secure或auth.log。这里记录的东西比很多人想的要多:SSH 登录成功与失败、su切换、sudo提权、PAM 模块的拒绝、甚至某些服务的认证失败。一条典型的成功登录长这样:
May 11 02:14:37 web01 sshd[24817]: Accepted password for deploy from 192.0.2.45 port 51422 ssh2从这个格式里能拆出时间、主机、进程、PID、事件类型、账号、来源地址、源端口。做统计的时候,我一般按来源地址和账号两个维度分别聚合,前者看有没有集中尝试,后者看有没有异常账号被使用。
第二块是auditd。很多机器装是装了,但没配规则,等于白装。检查方法很简单:
auditctl -l # 列出当前规则 systemctl status auditd # 确认服务在跑如果输出是No rules,那这份audit.log基本只有登录相关的少量记录,价值有限。有规则的情况下,查询姿势是这样的:
# 查关键文件的改动 ausearch -i -k passwd_change --start today # 查某个时间段内所有 execve 调用(进程创建) ausearch -i -m EXECVE --start "05/11/2024" "00:00:00" --end "05/11/2024" "06:00:00" # 汇总报告 aureport -i --start today --summaryausearch的-i参数一定要加,它会把数字形式的 UID、系统调用号翻译成人能读的名字,不加的话输出很难看。aureport则是做汇总的,比如按用户统计、按失败事件统计,适合快速判断“这段时间有没有异常密集的操作”。
第三块是 sudo 日志,它其实混在认证日志里,但值得单独拎出来看。sudo记录里会写清楚谁、在哪个终端、执行了什么命令。不过要注意,默认配置下sudo只记录命令本身,不记录命令的完整参数,除非你在sudoers里开了log_input和log_output。这个差异在排查时很关键——看到sudo vim和看到sudo vim /etc/ssh/sshd_config,性质完全不一样。
2.4 日志轮转与留存:为什么到了现场日志“刚好没了”
这是现场最让人血压升高的场景之一:你要查三天前的事,结果messages只有今天的内容。原因通常有两个,一个是轮转,一个是被人为清理。
轮转是正常机制,logrotate按天或按大小切分,旧文件压成.gz保留若干份。所以查历史日志时,别忘了看压缩包:
ls -lt /var/log/*.gz | head zgrep "Failed password" /var/log/secure-2024051*.gz zcat /var/log/messages-20240510.gz | grep -i "error"zgrep和zcat这两个命令在处理归档日志时非常好用,不用先解压。
人为清理就要警惕了。判断方法不复杂:看目录里文件的创建时间是否集中,看有没有“前面几天的日志缺失但更早的还在”这种反常情况,看/var/log目录本身的时间戳。如果一台机器messages从半年前一直保留到今天,中间某一天的记录整段消失,那基本不是轮转造成的。这时候要把这个发现记进时间线——日志缺失本身就是一条重要线索,它说明有人知道该抹掉什么。
还有一类机器压根没开日志服务,容器环境尤其常见。容器里跑的应用往往只往标准输出打日志,/var/log里空空如也,真正的记录在宿主机或者日志采集端。这种情况下去翻容器内的messages是白费功夫,得先搞清楚日志到底流向哪里。
3. 关键日志的实操分析手法
3.1 登录日志:从 failed 和 accepted 里筛出异常账号
登录日志是我每次必看的第一站,因为它能直接回答“人是怎么进来的”。但直接cat是没意义的,得做聚合。
先看失败尝试的分布,这一步能快速判断有没有集中尝试:
# 统计失败登录的来源地址 Top 10 grep "Failed password" /var/log/secure \ | awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' \ | sort | uniq -c | sort -rn | head -10这段awk的逻辑是:逐字段扫,找到from这个词,取它后面那个字段,也就是来源地址。之所以不写死字段位置,是因为不同服务、不同版本的日志格式字段数量不一样,写死位置很容易翻车。
再看成功登录的账号和时间:
# 统计成功登录的账号分布 grep "Accepted" /var/log/secure \ | awk '{print $(NF-5), $(NF-3)}' | sort | uniq -c | sort -rn然后做交叉:把失败次数很高的来源地址,和成功登录的来源地址做对比。如果某个地址失败了几百次之后出现了成功记录,那这条成功记录就是重点怀疑对象,需要立刻查这个账号在这之后做了什么。
还有几个容易被忽略的点。一是非交互式登录,比如通过 SSH 执行单条命令、通过scp传文件,这类行为在last里可能只显示极短的会话时长,但从业务角度看明显异常。二是本地登录与远程登录混用,last输出里tty1这类本地终端记录和pts/0这类远程终端记录要分开看。三是账号名的大小写和相似字符,有些账号名会故意做得和系统账号极像,比如把数字1写成字母l,肉眼看日志时很容易漏掉,用sort | uniq -c聚合一下就能暴露出来。
3.2 命令审计:bash_history 的坑与 auditd 的用法
bash_history是很多人第一反应会去看的东西,但它有几个硬伤,必须提前知道。
第一个硬伤是默认不带时间戳。你看到一串命令,但不知道是什么时候敲的,这在时间线重建里几乎是致命的。除非事先在/etc/profile里配了HISTTIMEFORMAT,否则这些命令没法排进时间轴。我现在的做法是给所有生产机器统一加上这个配置,代价很小,收益极大:
# 写入 /etc/profile.d/history.sh export HISTTIMEFORMAT="%F %T " export HISTSIZE=10000 export HISTFILESIZE=20000第二个硬伤是可以被绕过。用户在自己的 shell 里执行unset HISTFILE或者把HISTSIZE设成 0,后续命令就不落盘了。所以 history 是空的,不能证明没操作;history 有内容,也不能证明是全部操作。
第三个硬伤是多用户写入同一个文件。多个会话同时写~/.bash_history,退出时互相覆盖,记录会丢。
正因为这些坑,auditd才是命令审计的正道。一套最小可用的规则大概长这样:
# 监控关键文件写入与属性变更 auditctl -w /etc/passwd -p wa -k passwd_change auditctl -w /etc/shadow -p wa -k shadow_change auditctl -w /etc/ssh/sshd_config -p wa -k sshd_config auditctl -w /root/.ssh -p wa -k root_ssh auditctl -w /etc/cron.d -p wa -k cron_change # 监控特定目录下的执行行为 auditctl -w /tmp -p x -k tmp_exec-p后面跟的是权限位,w是写,a是属性变更,x是执行,r是读。日常排查里wa和x用得最多。-k是自定义标签,查询时用ausearch -k 标签名就能精准捞出来,比在几十万行日志里grep高效得多。
规则配好之后,重启会失效,得写进/etc/audit/rules.d/下的规则文件才能持久化。这个细节我见过好几次有人配完规则测通了,机器一重启全没了,白忙一场。
提示:
auditd的日志量可以很大,尤其在被监控目录访问频繁的机器上。上生产前先估算一下增长速率,别把磁盘写满了,那反而成了新的事故。
3.3 持久化痕迹:计划任务、服务、启动项的时间戳关联
攻击者落地之后,绝大多数情况下会想办法维持访问,这就留下持久化痕迹。这类痕迹的判断,靠的不是日志里写了什么,而是文件的时间戳和系统记录能不能对上。
重点检查的位置有几个:
/etc/cron.d/、/etc/cron.hourly/、/var/spool/cron/(用户级 crontab)/etc/systemd/system/、/usr/lib/systemd/system/下的自定义 unit/etc/rc.local、/etc/profile、/etc/bashrc这类开机或登录时执行的脚本/etc/ld.so.preload,这个文件一旦存在且非空,就要高度警惕
检查命令我一般这么组合:
# 按修改时间倒序列出关键目录下的文件 ls -lt --time-style=full-iso /etc/cron.d/ /etc/systemd/system/ /etc/profile.d/ # 看文件的三类时间戳 stat /etc/cron.d/suspicious_taskstat输出里的Modify、Change、Access三个时间值得反复琢磨。Modify是内容最后修改时间,Change是元数据(权限、属主)变更时间,Access是最后访问时间。很多人只知道看Modify,但Change在判断“文件被改过属主或权限”时是关键依据。更关键的是,Change时间在常规手段下改不了,除非动系统时间或者用特殊方法,所以它是时间戳里相对最硬的那个。
这里有个实操技巧:把/etc目录下所有文件按Change时间排序,人工筛一遍最近几天的变更,往往能捞出一些“不该在这时候被改”的文件。
find /etc -type f -newerct "2024-05-10 00:00:00" ! -newerct "2024-05-12 00:00:00" \ -printf "%T@ %Tc %p\n" | sort -n-newerct是按Change时间筛选,-printf把时间戳和路径一起打出来,sort -n按时间排。这一条命令跑下来,短时间内被动过的配置文件基本无所遁形。
3.4 Web 与服务日志同系统日志的交叉验证
现在的应急场景里,纯主机被入侵的比例在下降,更多是从应用层打进来的。所以应用日志和系统日志的交叉验证,比单看任何一边都有价值。
思路是这样的:应用日志告诉你“有请求进来了”,系统日志告诉你“这个请求导致了什么”。比如 Web 访问日志里出现一段明显异常的参数,对应的系统日志在同一秒出现了一条sh进程被创建的最审计记录,这两条一撞,链路就通了。
具体操作上,我一般先定位时间窗口,然后在两边分别抓:
# 应用侧:找出响应体异常大的请求,常见于数据外带 awk '{print $4, $7, $10}' /var/log/nginx/access.log \ | awk '$3 > 1048576 {print}' | head -20 # 系统侧:同一时段内所有由 Web 服务用户发起的进程 ausearch -i -m EXECVE --start "05/11/2024" "02:00:00" --end "05/11/2024" "03:00:00" \ | grep -i "www-data\|nginx\|apache"第一条命令里$3是响应体大小(以字节计),超过 1MB 就算偏大,这个阈值按业务实际情况调。第二条命令是按时间窗口捞进程创建记录,再过滤出由 Web 服务运行用户发起的那些。
还有一个高频场景是中间件日志和系统日志的时间差。应用日志多半是本地时区,journalctl默认也是本地时区,但某些容器基础镜像默认是 UTC,两边一混就会差 8 小时。这个坑我在两个项目里都遇到过,第一次排查了半天以为是日志缺失,后来一算时区才反应过来。
3.5 文本处理三件套:grep、awk、sort/uniq 的组合拳
工具不用多,grep、awk、sort、uniq、cut、sed这六个足够了,关键是组合方式。我把自己常用的几条整理成一个速查表,现场直接改参数就能用。
| 目的 | 命令要点 |
|---|---|
| 按 IP 统计失败登录 | grep "Failed" 文件 | awk '{...}' | sort | uniq -c | sort -rn |
| 提取指定时间段 | awk '$1" "$2 >= "May 11 02:00" && $1" "$2 <= "May 11 03:00"' |
| 找高频出现的路径 | awk '{print $NF}' 文件 | sort | uniq -c | sort -rn | head |
| 多文件合并去重 | sort -u 甲文件 乙文件 > 合并结果 |
| 按某一列连接两个日志 | join -1 2 -2 1 <(sort -k2 甲) <(sort -k1 乙) |
join这个命令用得人少,但在关联两份日志时特别好用。比如一份是登录记录(列里有账号名),一份是账号变更记录(列里也有账号名),按账号名 join 一下,就能找出“刚被创建就又登录了”的账号。用之前两边都得先sort好,join只处理有序输入,这个前提忘了会得到莫名其妙的结果。
还有个小技巧:处理大文件时用LC_ALL=C grep能明显提速,因为省掉了多字节字符的处理开销。日志动辄几百兆的时候,这个差别能到几倍。
4. 时间线构建与证据固化
4.1 时间对齐:时区、格式、时钟漂移三件事
时间线是应急响应的最终产物,而时间线最容易错的地方,恰恰是时间本身。我把它拆成三个要处理的问题。
时区问题。日志里出现的May 11 02:14:37,到底是本地时间还是 UTC?syslog默认写本地时间且不带时区标识,journalctl默认也按本地时区渲染,但底层存的是 UTC 时间戳。如果机器时区配错了,或者中途改过时区,那段日志的绝对时间就不可信了。处理方式是:先确认机器当前时区,再看/etc/localtime指向,最后和外部参照(比如负载均衡的访问日志时间)对一下。
timedatectl ls -l /etc/localtime date -d "2024-05-11 02:14:37" +%s # 转成 epoch,方便做偏移计算用 epoch 时间戳做统一存储是个好习惯,它没有时区和格式的歧义,做加减也方便。
格式问题。syslog是月 日 时:分:秒,journalctl可以输出 ISO8601,nginx默认是日/月/年:时:分:秒 +0800,应用日志五花八门。做统一处理时,我一般写一个小的转换函数,把各种格式都转成YYYY-MM-DD HH:MM:SS的字符串,后面再排序。
时钟漂移。这条最阴。机器如果没配时间同步,或者同步服务出过问题,它的时间可能和真实时间差几分钟甚至几小时。判断方法是看日志里有没有时间跳变,比如前一条是02:14:37,下一条突然回到01:52:10,那这段的连续性就存疑。还有一个办法是对比last reboot的输出和外部监控记录的宕机时间,如果对不上,说明这台机器的时钟有问题。
注意:时间不可信这件事必须在报告里明确写出来。很多人为了报告“好看”,把不确定的时间当确定时间写,这在后续复盘时会造成很大麻烦。
4.2 用脚本把多源日志压成一张时间线表
手工拼时间线,超过二十条记录就会乱。我的做法是写个脚本,把各来源的记录统一成四列:时间、来源、主体、事件描述,输出成 CSV,然后用表格软件排序看。
下面这个脚本是我常用的骨架,改改路径就能跑:
#!/bin/bash # 时间线提取骨架 OUT="timeline_$(date +%Y%m%d_%H%M%S).csv" echo "time,source,subject,event" > "$OUT" START="2024-05-11 00:00:00" END="2024-05-11 06:00:00" # 认证日志 awk -v s="$START" -v e="$END" ' { ts = $1" "$2" "$3 if (ts >= s && ts <= e) { sub(/^[A-Z][a-z]+ +[0-9]+ /, "", $0) print ts",auth,,"$0 } }' /var/log/secure >> "$OUT" # 审计日志按时间导出 ausearch -i -ts "05/11/2024" "00:00:00" -te "05/11/2024" "06:00:00" 2>/dev/null \ | awk '/^time->/{t=$3" "$4; print t",audit,,"}' >> "$OUT" # 关键文件的元信息 for f in /etc/passwd /etc/shadow /etc/ssh/sshd_config; do ts=$(stat -c "%y" "$f" | cut -d. -f1) echo "$ts,fs,$f,内容或属性发生过变更" >> "$OUT" done sort -t, -k1 "$OUT" -o "$OUT"这个脚本做得比较粗,但它解决了一个核心问题:把所有东西压到同一条轴上。实际使用中,我会再补上进程创建、网络连接、Web 访问这几类,字段也按需扩展。关键是先有统一格式,细节可以逐步加。
生成 CSV 之后,用表格软件按时间排序,把连续的、属于同一主体的记录标成一组,事件脉络就浮现出来了。我第一次用这个方法的时候,原本以为要查一天的东西,两小时就理清了。
4.3 证据保全的实操细节
应急响应做完,如果后续要走流程,证据的完整性就是硬要求。这部分不复杂,但有几点必须做到。
先镜像,再分析。能在物理机上做的最好用dd做全盘镜像,做不到的至少把/var/log、/etc、/root、/tmp这几个目录打包。打包前算一遍哈希,打包后再算一遍,两个值一致才说明传输过程没出错。
# 打包并计算哈希 tar -czf evidence_log.tar.gz /var/log /etc /root/.bash_history 2>/dev/null sha256sum evidence_log.tar.gz | tee evidence_log.sha256 # 只读挂载,避免分析时污染时间戳 mount -o ro,noatime /dev/sdb1 /mnt/evidencenoatime这个参数值得说一句。默认的挂载方式下,你读一个文件就会更新它的Access时间,分析过程本身就把证据改了。用ro,noatime挂载,能最大限度保住原始状态。
记操作日志。从到现场第一分钟起,把敲过的每条命令、执行的时间记下来。这个习惯我一开始觉得麻烦,后来发现它的价值有两个:一是别人能复现你的路径,二是在报告里写“基于这些操作得出这些结论”时,自己能说得清。
保留原始文件。分析时用副本,原件不动。尤其是btmp、wtmp这种二进制日志,不同工具读出来的结果可能略有差异,原件留着才有回旋余地。
5. 踩过的坑与排查技巧实录
5.1 日志被清理或篡改时怎么看出来
这一点我在前面提过,值得单独展开,因为“日志被清了”和“本来就没什么日志”在表现上很像,但含义完全不同。
判断依据主要有几个。一是文件的Change时间集中在某个时刻大量变化。正常的日志轮转是有规律的,一天一次或按大小触发,但如果是某个凌晨三点突然/var/log下一堆文件的时间戳全部更新,那就反常。二是日志连续性中断。syslog是按时间顺序追加写的,如果中间某一段凭空消失,而前后两天都正常,这基本可以确认是人为删的。三是文件大小与轮转参数对不上。配了按 100MB 轮转,结果当前文件只有 2MB 而历史文件全没了,也说不过去。四是权限或属主变了。正常日志由root:root或root:adm持有,如果变成其他属主,就要警惕。
还有一个不太容易注意到的点:日志服务的运行时间。如果rsyslogd的运行时长比系统运行时长短很多,说明它中途重启过,中间那段可能就没记。这个用systemctl status rsyslog就能看到。
发现这些迹象之后,处理方式是:把它当作结论的一部分写进报告,而不是当作“证据不足”。因为能精准清理日志的人,一定知道系统里有什么,这本身就是很强的指向性。
5.2 几类高频误判,别急着下结论
我在实际项目里见过、自己也犯过的误判,主要有这么几类。
把监控探活当攻击。很多监控系统会用固定的账号定期登录做健康检查,一旦密码轮换没同步,就会产生大量失败登录。这类失败的特征是来源地址固定、间隔规律、目标账号固定,一看时间分布就能分辨。我一般的判断标准是看间隔是否均匀,攻击性的尝试间隔通常随机或者逐次递增。
把密码过期当入侵。账号密码过期后,用户反复尝试登录,会产生一连串失败记录,紧接着可能有管理员帮他重置,然后出现成功登录。这个序列和攻击很像,区别在于来源地址通常在合理范围内、时间在正常工作时间。
把自动化脚本当可疑命令。批量运维脚本、定时备份脚本,往往会在短时间内产生大量进程创建记录。如果不结合计划任务的配置一起看,很容易误判。
忽略容器带来的重叠。容器里跑的服务,它产生的日志可能同时出现在容器内的文件和宿主机上的journal里,看起来像“两条相同记录”,容易把它当成两次事件。判断方法是看 PID 和命名空间相关的字段。
把正常的证书更新当作网络异常。证书轮换、软件源同步这类操作会带来外联行为,如果不了解业务节奏就会误判。这也是为什么排查前一定要找业务方确认“最近有没有做过发布或者配置变更”。
5.3 常见问题速查表
下面这张表是我自己整理的,现场遇到问题时先扫一眼,能省不少时间。
| 现象 | 可能原因 | 排查入手点 |
|---|---|---|
/var/log下文件很少或为空 | 容器环境、日志外发、被清理 | 看是否容器、查 rsyslog 配置、看目录时间戳 |
| 日志时间与真实时间差很多 | 时区配置错、时钟不同步 | timedatectl、date、对比外部参照 |
last输出为空 | wtmp被清或权限异常 | 检查文件大小与属主、对比lastlog |
auth.log有大量失败记录 | 集中尝试、密码轮换未同步 | 按来源和账号聚合、看时间间隔规律 |
auditd没有输出 | 服务未启动、规则为空 | systemctl status auditd、auditctl -l |
| 历史命令缺失 | 未配置记录、被主动关闭 | 检查/etc/profile配置、看其他用户是否正常 |
| 关键文件时间戳异常集中 | 批量部署、人为改动 | find按ctime排序、对照变更记录 |
| 日志出现乱码时间 | 编码或格式混用 | 确认日志来源、统一转换后再排序 |
5.4 一个容易被忽略的补充动作
最后补一个我自己养成的小习惯:每次排查结束,不管结论是什么,都把这台机器的日志留存情况、时间同步状态、审计规则配置这三项写进一份简短记录。原因是这类问题往往是系统性的,同一批机器很可能有同样的配置缺陷。我遇到过一台机器疑似被入侵,查了一圈发现只是时钟漂移导致监控误报,但顺手检查同批次的另外十几台,发现有三台压根没开审计,还有两台日志只保留一天。这些隐患如果不在这次排查中暴露,下次真出事的时候就只能干瞪眼。
排查这件事,单次结论的价值其实有限,更值钱的是借这次机会把整套观测能力补齐。日志留存时间从一天拉到三十天、给关键目录补上审计规则、统一时区配置,这几件事做下来成本不高,但下一次再被叫起来的时候,手里能用的东西就完全不一样了。