服务器被入侵了,你查得出谁干的吗
几乎每个个人站长都遇到过这样的时刻:某天早上发现网站首页被换了内容,或者 top 里跑着一个不认识的进程占满 CPU。第一反应通常是 last、who、翻一下 /var/log/secure,然后发现——查不出任何东西。日志被清了,异常登录记录没有,那个进程的执行文件路径指向一个早就被删掉的 /tmp/xxx。
这不是你水平不够,而是默认的 Linux 日志系统根本不足以做安全溯源。syslog 记录的是「服务层」的事件(谁登录了、服务启动没启动),而攻击者关心的那些行为——执行了哪个二进制、修改了哪个文件、发起了哪个网络连接、提权时调用了哪个系统调用——syslog 一概不记。
这就是 auditd 存在的意义。它工作在 Linux 内核的审计子系统上,能记录到文件访问、系统调用、命令执行、权限变更这一层。这篇文章从部署到规则编写,再到真正出事时怎么用它做溯源,完整走一遍。
auditd 和 syslog 到底差在哪
理解这个差别,才知道什么时候该指望它。syslog(rsyslog / journald)的数据来源是各个用户态程序主动上报的日志,粒度取决于程序写了什么。SSH 登录失败会记录,因为 sshd 主动写了日志;但某个用户用 curl 下载了一个文件,syslog 完全不知道,因为 curl 不写日志。
auditd 不同。它的数据来源是内核的 audit subsystem。当你在内核里挂上规则,指定「监视 /etc/passwd 的写操作」,那么无论是谁、用哪个程序、什么时候写,内核都会生成一条审计记录。规则的粒度可以细到单个系统调用号(比如 execve、openat、connect),这是用户态日志永远做不到的。
代价是性能开销和磁盘占用。auditd 每条记录都不小,如果规则开得太宽(比如监视整个 /etc 并且打开所有系统调用),在高负载服务器上会产生惊人的日志量,进而拖慢 IO,甚至把日志分区写满——这时候 auditd 自己会因为写不进去而开始丢事件,审计反而失效。
所以正确的心态是:不要追求「全记住」,而是有选择地记住关键行为。审计的目标是「出事时够用」,不是「记录一切」。
安装与基础配置
Debian / Ubuntu 上:
apt update && apt install -y auditd audispd-plugins
systemctl enable --now auditd
systemctl status auditdCentOS / RHEL 上:
yum install -y audit
systemctl enable --now auditd装完先确认内核是否支持并启用了审计(有些精简镜像或者容器环境会关掉):
# 查询当前审计状态
auditctl -s
# 关注这几个字段
# enabled 1 -> 已启用
# backlog 0 -> 等待处理的审计事件队列长度,长期不为 0 说明规则过重
# lost 0 -> 内核丢弃的事件数,非 0 说明 auditd 处理不过来,必须减规则
# rate_limit 0 -> 每秒最大消息数限制,0 表示不限backlog 和 lost 这两个值要定期看。我在一台流量中等的小站服务器上一开始挂了「监视 /etc 全部写操作」的规则,跑了两天 lost 就涨到几万条——意味着最关键的那几条入侵记录可能恰恰被丢了。后来把规则收敛到只监视具体的关键文件,lost 才稳定在 0。
主配置文件在 /etc/audit/auditd.conf,个人站长必调的几项:
# 日志保留策略:按大小轮转还是按天
max_log_file = 50 # 单个日志文件最大 50MB
num_logs = 10 # 保留 10 个历史文件
max_log_file_action = ROTATE
# 磁盘写满时的行为,个人站建议 keep_logs 而不是 halt
# halt 会让系统直接停机(安全但影响可用性)
space_left_action = SYSLOG
admin_space_left_action = SUSPEND
disk_full_action = ROTATE生产环境里 disk_full_action = halt 是合规要求(保证绝不丢审计),但对个人站长来说,站点直接宕机比丢几条日志更难受。折中方案是把 audit 日志单独放一个分区,让它写满自己那个分区,不影响系统盘。
规则编写:从命令行到持久化
先记住一个关键点:auditctl 添加的规则重启就没了。持久化要写进 /etc/audit/rules.d/*.rules,然后 augenrules --load 加载。
命令行适合临时调试:
# 监视 /etc/passwd 的写和属性修改
auditctl -w /etc/passwd -p wa -k identity
# -w 指定路径
# -p 指定权限类型:r读 w写 x执行 a属性修改
# -k 是关键字(key),用于后续检索,必须起有意义的名字
# 查看当前所有规则
auditctl -l
# 删除所有规则(调试时用,慎用)
auditctl -D真正要落地的是文件里的规则。一份面向个人站长的实用规则集:
# /etc/audit/rules.d/zz1984.rules
# ---- 第一条:缓冲区大小,防止高负载下丢事件 ----
-b 8192
# ---- 关键身份文件 ----
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# ---- SSH 配置 ----
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /root/.ssh/ -p wa -k ssh_keys
# ---- 定时任务(持久化后门最爱藏的地方)----
-w /etc/crontab -p wa -k cron
-w /etc/cron.d/ -p wa -k cron
-w /etc/cron.daily/ -p wa -k cron
-w /etc/cron.hourly/ -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# ---- 系统服务与启动项 ----
-w /etc/systemd/system/ -p wa -k systemd
-w /etc/rc.local -p wa -k startup
# ---- 命令执行(最重要也最耗资源的一条)----
-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=-1 -k exec_cmd
# ---- 提权行为 ----
-a always,exit -F arch=b64 -S setuid,setgid -F auid>=1000 -k privilege
# ---- 网络连接(可选,量大,按需开)----
# -a always,exit -F arch=b64 -S connect -F auid>=1000 -k network
# ---- 让规则不可被篡改(重启前生效,最后加载)----
-e 2逐条解释几个容易踩坑的地方。
-b 8192 是内核审计缓冲区大小(单位是 4KB 页,所以实际是 32MB)。默认值往往偏小,稍忙一点的服务器就会丢事件。这个值加到 8192 或 16384 是常见做法,代价是多占几十 MB 内存。
-F auid>=1000 是过滤条件,表示只记录 UID 大于等于 1000 的(也就是普通用户)的操作。这条过滤极其重要——如果不过滤,root 跑的所有系统任务(日志轮转、包管理、定时任务)都会产生巨大的 execve 记录。加上它之后,日志量能降一个数量级,而攻击者如果只是通过普通账号进来,依然会被完整记录。不过要注意:如果攻击者一开始就拿到 root,这条就绕过了,所以关键文件监视(上面那堆 -w)不能省,它们不区分用户。
auid!=-1 排除掉那些没有审计 ID 的进程(内核线程等),否则会有大量干扰记录。
最后的 -e 2 把审计设为不可变模式。一旦设置,在当前运行周期内无法再修改规则,只能重启解除。这是防攻击者关掉审计的手段,但也会让你调试时很痛苦——所以调试阶段先别加这行,规则稳定了再补上。
加载规则:
# 检查语法
augenrules --check
# 加载
augenrules --load
# 确认生效
auditctl -l | head -20
cat /etc/audit/rules.d/zz1984.rules | grep -c "^-"出事了怎么查:ausearch 实战
规则挂了不是终点,能查出来才是。auditd 的查询主力是 ausearch。
场景一:密码文件被改了,谁改的?
ausearch -k identity -i-k identity 对应规则里的 key,-i 表示把原始数字(UID、GID、时间戳)翻译成人能读的形式。输出里会看到 comm(命令名)、exe(可执行文件路径)、auid(原始登录用户)、uid(当前有效用户)、tty(终端)。这几个字段是定位人的核心:auid 是「谁最初登录进来的」,uid 是「执行时的身份」,两者不一致往往意味着提权。
场景二:某个可疑账号最近执行过什么?
ausearch -ua 1001 -ts recent -i-ua 按 auid 过滤,-ts recent 只看最近十分钟。想指定时间范围用 -ts today 或者 -ts 09/16/2026 00:00:00 -te 09/16/2026 12:00:00。
场景三:某天某个时间段,系统里执行了哪些命令?
ausearch -k exec_cmd -ts today -i | grep -E "comm=|exe=" | head -50如果你怀疑某个时间段被入侵,逐条看 exe= 字段。正常的运维命令都在 /usr/bin、/usr/sbin、/bin 下;一旦看到 exe=/tmp/xxx、exe=/dev/shm/xxx、exe=/var/tmp/xxx,基本可以确定是恶意程序——正规程序不会从那几个目录跑,而那正是攻击者最爱落地的地方,因为它们通常可写且不占持久存储。
场景四:只看失败和异常。
# 权限被拒绝的操作
ausearch --success no -ts today -i | head -30
# 组合过滤:某用户 + 失败的操作
ausearch -ua 1001 --success no -ts recent -i场景五:生成汇总报表。
aureport --summary # 总体概览
aureport -au # 按认证事件统计
aureport -x --summary # 按可执行文件名统计(找出最常跑的命令)
aureport -f # 文件访问统计
aureport -l # 登录事件aureport -x --summary 特别有用。它会按可执行文件名汇总所有执行记录,你可以一眼看出系统里跑得最多的是什么。如果里面冒出一个你从没见过的名字、而且执行了几百次,那就是收获。
让日志真正「留得住」
审计做得好不好,一半看规则,一半看日志能不能活到你需要它的那天。攻击者拿到 root 后第一件事通常就是清日志,如果你的审计日志只存在本地同一块盘上,那前面所有工作都白做。
几个必须做的加固:
一是异地实时转发。配置 audisp-remote 插件,把审计事件实时推到另一台服务器或者日志收集端。这样即使本机被清盘,远端依然有完整记录:
# /etc/audit/plugins.d/au-remote.conf
active = yes
direction = outbound
path = /sbin/audisp-remote
type = always
# /etc/audit/audisp-remote.conf
remote_server = 你的日志服务器IP
port = 60个人站长如果只有一台服务器,退而求其次:把日志同步到对象存储(比如用 rclone 定时上传),或者至少推一份到自己另一个域名的邮箱里。关键是要跨出被入侵的那台机器。
二是给日志目录加不可变属性。审计日志在 /var/log/audit/,可以让它连 root 都不能随便改:
chattr +a /var/log/audit/audit.log+a 表示 append-only,只能追加不能删除或覆盖。注意这个属性会让 logrotate 需要特殊处理,否则轮转时会失败——通常的做法是轮转脚本里先 chattr -a,轮转完再 chattr +a。
三是常规日志一起保住。auditd 记录的是内核层的行为,但认证、Web 访问这些还得靠 syslog 和 Nginx 日志。把它们一起纳入异地备份,形成完整的证据链。auditd 里记录的那个 /tmp/xxx 进程,配合 Nginx 访问日志里对应的那个恶意请求,才能把「怎么进来的、进来干了什么」串起来。
小结:审计的价值在于「平时无用,出事救命」
auditd 这类东西有个特点:没出事的时候,它只占资源不产出任何你日常会看的东西,很容易被当成可有可无的负担,甚至有人在服务器卡顿时第一个把它关掉。但真正的安全事故只有两种结果——查得出来和查不出来。查不出来意味着你不知道漏洞在哪、不知道数据被拿了多少、不知道该不该通知用户,只能在不确定中反复提心吊胆。
对个人站长来说,配置成本其实不高:花半小时把上面那份规则集抄进去,再花十分钟设置好日志外发,剩下的交给系统自动跑。真出事的那天,你会庆幸这几十分钟没有省。
建议先用调试模式跑一周,观察 auditctl -s 里的 lost 是否保持为 0,确认日志量在可接受范围(一般个人站每天几 MB 到几十 MB),然后再把 -e 2 加上锁定规则。