CentOS 7 或 CentOS Stream 的运维人员经常面临一个尴尬局面:服务器被通报有异常外联,但登录检查时,网络连接早已断开,/var/log/messages 里空空如也,常规的 ss 或 netstat 只能看到当前状态。要回溯几小时甚至几天前的网络连接趋势,sysstat 工具包中默默运行的 sar 数据往往是唯一的救命稻草。很多人只知道 sar 能看 CPU 和内存,却忽略了它记录的网络统计信息足以拼凑出一张完整的异常连接时间线。

理解 sar 网络数据采集的底层机制

sysstat 的 sa1 脚本通常由 cron 每分钟或每十分钟调用一次,它从 /proc/net/dev、/proc/net/snmp、/proc/net/ip_vs_stats 等内核接口抓取瞬时计数,追加写入 /var/log/sa/saDD 二进制文件中。这意味着 sar 记录的不是连接快照,而是网络接口的累计收发字节数、报文数、压缩错误、冲突等指标,以及 TCP 段层面的主动连接数、被动连接数、重传段数、复位连接数等关键状态。这些累计值通过两次采样之间的差值,就能还原出任意时间段的流量变化和连接行为模式。

从 /proc/net/snmp 提取 TCP 连接异常信号

执行 sar -n TCP 时,系统读取的是 /proc/net/snmp 中的 Tcp 行,重点关注以下几个字段:ActiveOpens 表示主动发起的连接数,PassiveOpens 表示被动接受的连接数,InSegs 和 OutSegs 是收发 TCP 段数,RetransSegs 是重传段数,EstabResets 是已建立连接中的复位次数。如果某个时间段内 ActiveOpens 突然从每分钟几十次飙升到数千次,而 PassiveOpens 几乎不变,基本可以判定服务器在对外发起大量连接,这往往是反弹 shell、挖矿木马或扫描器的典型行为。同理,EstabResets 的突增意味着大量连接被 RST 强行终止,可能是端口扫描被防火墙拦截,也可能是外联 C2 服务器时被对方重置。

利用 sar -n DEV 追踪流量突变的物理接口

sar -n DEV 输出的 rxkB/s 和 txkB/s 能精确到每个网络接口的收发速率。当怀疑服务器对外发起数据外传时,重点观察 eth0 或 ens192 等外网接口的 txkB/s 是否在非业务高峰期出现持续高位。例如凌晨三点业务几乎无流量,但 txkB/s 却稳定在 10MB/s 以上,且 rxpck/s 和 txpck/s 的报文数不成比例,这极有可能是数据被打包外泄。配合 sar -n EDEV 查看各接口的错误包和丢弃包,如果 txerr/s 或 rxdrop/s 异常增高,说明网卡队列或驱动层面出现拥塞,可能是 DoS 攻击或异常流量过大导致。

精准回溯历史时刻的 sar 数据

sar 默认读取当天的 saDD 文件,要查看历史数据,必须用 -f 参数指定归档文件路径。例如查看三天前下午两点到四点之间的网络统计:

sar -n TCP -f /var/log/sa/sa03 -s 14:00:00 -e 16:00:00

如果系统保留的 sa 文件被压缩成 sa03.xz,需要先用 xzcat 解压到临时目录再读取。很多运维人员不知道 sar 支持 -s 和 -e 时间过滤,直接在整天的数据里翻找,效率极低。更精细的做法是用 -f 配合多个 sa 文件,写一个简单的 for 循环批量提取关键指标:

for i in {01..07}; do
  echo "=== Day $i ===" >> /tmp/tcp_analysis.txt
  sar -n TCP -f /var/log/sa/sa$i -s 00:00:00 -e 23:59:59 >> /tmp/tcp_analysis.txt
done

这样就能生成连续一周的 TCP 连接趋势报告,用 Excel 或 Python 绘图后,异常峰值一目了然。

交叉对比网络指标锁定攻击类型

单一指标异常只能说明有问题,交叉对比才能定性。当 ActiveOpens 暴增的同时,如果 RetransSegs/OutSegs 的比率超过 5%,说明外发连接质量极差,可能是扫描器在大量探测不存在的 IP 或端口。如果 PassiveOpens 暴增且 EstabResets 同步升高,说明有人在爆破 SSH、MySQL 等服务,大量连接被建立后又被服务端或防火墙 RST 掉。再结合 sar -n IP 查看 ipInDelivers 和 ipOutRequests,如果入向报文数远大于出向,且 Forwarding 字段不为零,说明服务器可能在充当跳板转发流量,这是内网渗透的典型特征。

通过 sar -n SOCK 统计 socket 状态分布

sar -n SOCK 能显示 totsck(总套接字数)、tcpsck(TCP 套接字数)、udpsck(UDP 套接字数)等。如果 tcpsck 在某个时间点从几百激增到数万,且后续迅速回落,说明发生了短时间内的连接风暴。这种模式常见于 HTTP 连接池耗尽、Redis 或 MySQL 被大量短连接打满。配合 sar -n TCP 中的 timewait 套接字数,如果 timewait 数量异常高,说明服务器在主动关闭大量连接,可能是对外发起的短连接攻击或爬虫行为。

从重传统计中识别隐蔽隧道

DNS 隧道和 ICMP 隧道在 sar -n DEV 中表现得很隐蔽,因为流量通常不大。但 sar -n UDP 中的 udpInDatagrams 和 udpOutDatagrams 会暴露端倪。如果一台平时几乎不用 UDP 的 Web 服务器,udpOutDatagrams 突然持续有规律地增长,且目标端口集中在 53 以外的高位端口,极可能是 DNS 隧道的外传行为。再结合 sar -n ICMP 查看 icmpOutEchos 和 icmpInEchos,如果 ICMP 报文大小异常且频率固定,就是典型的 ICMP 隧道。这些指标单独看都不起眼,但放在时间轴上对比,隐蔽通道的周期性心跳特征就会暴露。

自动化异常检测与告警脚本

人工每天翻 sar 数据不现实,必须建立自动化基线检测。核心思路是用 sadf 命令将二进制 sa 文件转换为 JSON 或 CSV,再用脚本计算滑动窗口内的均值和标准差,超过三倍标准差即告警。示例脚本片段:

sadf -j /var/log/sa/sa$(date +%d) -- -n TCP | jq '.[] | select(.fields.activeopens > 1000)'

这个命令能直接输出当天 activeopens 超过 1000 的所有采样点。更完善的方案是用 Python 的 pandas 库读取 sadf 输出的 CSV,对每个指标建立 7 天基线,动态计算异常阈值。将脚本加入 cron 每十分钟执行一次,检测到异常时通过企业微信或钉钉机器人发送告警,附带具体时间点和指标数值,这样就能在异常外联发生的几分钟内收到通知,而不是等通报了再去翻历史。

sar 数据保留策略与取证合规

默认 sysstat 只保留最近 7 天的数据,这个周期对于安全回溯远远不够。建议修改 /etc/sysconfig/sysstat 中的 HISTORY 参数为 30 或 90,同时调整 /etc/cron.d/sysstat 中的 sa2 脚本,将日归档文件在压缩前同步到专用的安全审计目录,并设置只读权限防止攻击者篡改。sa 文件本质是二进制格式,无法直接编辑,但攻击者可能会删除或覆盖整个文件。因此建议用 rsync 实时同步到远程日志服务器,或在本地用 auditd 监控 /var/log/sa/ 目录的写操作。一旦发生安全事件,这些 sa 文件就是追溯攻击时间线、还原网络行为的核心证据。

实战案例:通过 sar 数据还原挖矿木马外联时间线

某台 CentOS 7 服务器在凌晨 2:17 被安全设备告警有对外 4444 端口的连接。登录后发现 CPU 正常,top 里没有异常进程,但 ss -antp 里有一个 ESTABLISHED 状态的连接指向外部 IP 的 4444 端口,进程名显示为 [kworker/0:0]。这显然是内核态 rootkit 隐藏了进程。通过 sar -n TCP -f /var/log/sa/sa$(date +%d) -s 01:00:00 -e 03:00:00 发现,activeopens 从 1:47 开始从每分钟 3 次逐步上升到 2:15 的每分钟 120 次,随后稳定在 80 次左右。sar -n DEV 显示 eth0 的 txkB/s 从 2:17 开始维持在 200KB/s,rxkB/s 仅 5KB/s,典型的计算任务下发和数据上传模式。再往前追溯三天的 sa 文件,发现同样的模式在三天前的凌晨 3:12 首次出现,持续了约四十分钟后消失。这个时间线与安全设备的第一次告警完全吻合。最终根据 sar 数据确定入侵时间窗口为三天前的 3:10-3:15,攻击者通过 Redis 未授权访问写入 SSH 公钥后植入 rootkit 和挖矿程序,每天凌晨定时启动外联。

sar 与其它工具的互补定位

sar 记录的是系统级聚合指标,看不到具体连接的四元组信息,这是它的局限性。因此 sar 的定位是趋势发现和异常时间段锁定,而不是连接详情取证。当 sar 发现某个时间段异常后,应该立刻去翻对应时间的 /var/log/audit/audit.log(如果开启了 auditd)、/var/log/secure 中的 SSH 登录记录、以及应用日志中的请求记录。如果系统安装了 nethogs 或 iftop 的历史记录工具,也可以交叉验证。但绝大多数场景下,只有 sar 是默认开启且持续运行的,这就是它不可替代的价值。

深入理解 /proc 文件系统与 sar 的采样缺陷

sar 读取的 /proc/net/snmp 和 /proc/net/dev 是内核维护的累计计数器,这些计数器在系统重启后会清零,在极端流量下可能发生 32 位回绕。CentOS 7 的内核已经使用 64 位计数器,但某些虚拟化环境或老旧网卡驱动仍可能使用 32 位计数。如果 sar 输出的数值出现巨大的负值或断崖式下跌,多半是计数器回绕导致。这时需要结合 sar 的采样间隔判断,如果两次采样之间流量不可能超过 4GB(32 位上限),那负值就是回绕,手动加上 2^32 即可修正。另外 /proc 文件系统在读取时存在瞬时不一致问题,sar 单次采样可能读到某个连接状态变化的中间态,因此不要过度解读单个采样点的毛刺,要看连续三个以上采样点的趋势。

定制 sar 采集粒度捕捉短时突发

默认 sa1 每十分钟采集一次,对于持续时间只有几十秒的扫描或爆破行为,这个粒度太粗。可以修改 /etc/cron.d/sysstat 中的 sa1 脚本,将采集间隔改为每分钟甚至 30 秒:

* * * * * root /usr/lib64/sa/sa1 1 1

这样每分钟采集一次,每天的数据文件会增大但仍在可控范围。对于需要更细粒度的场景,可以用 sar 的实时模式配合 -o 参数将输出直接写入自定义文件:

sar -n TCP 1 3600 -o /var/log/sa/custom_high_freq &

这个命令每秒采集一次,持续一小时,适合在应急响应时临时部署,捕捉攻击者的每一次连接尝试。