凌晨两点收到一条 CPU 使用率告警,登录服务器后top显示某个进程把整台机器吃满,名字却陌生得像随机生成的字符串;翻/var/log/secure又看到一整页来自同一网段的Failed password记录。这种时候,“追杀这个服务器的恶人”就成了一句特别具体的运维口令。
这里的“恶人”,并不是某个具名的人,而是服务器上不该存在的异常主体:可能是 CPU 飙高的恶意进程,可能是爆破 SSH 的攻击源,可能是被恶意写入的定时任务,也可能是一段长时间占用数据库连接、让整个业务卡死的慢查询会话。本文会按照一次真实的“服务器狩猎”顺序,从进程、登录、网络、数据库四个层面,说明如何定位异常主体、保留证据、完成处置,并在清理之后做好加固,避免下一次再被同一类问题找上门。
适用读者包括需要独立维护 Linux 服务器的运维工程师、后端开发,以及正在学习服务器排障的新人。文章给出的命令以 CentOS/RHEL 系为例,Ubuntu 等 Debian 系只需把/var/log/secure换成/var/log/auth.log,思路完全一致。
1. 先给“恶人”画个像:异常主体可能藏在哪里
很多新人在收到告警后第一反应是“哪个进程 CPU 高就 kill 哪个”。这个思路不算错,但容易漏掉真正的问题。为了不在一开始就被某个假象带偏,建议先理解异常主体可能存在的四类位置。
1.1 恶人不只是 CPU 高的进程
第一类是操作系统层。最直观的表现是 CPU 或内存异常上升,top里能看到一个高占用进程,或者在ps中出现随机名称、乱码名称、明显不属于你部署服务的命令行。但这里有个容易被忽略的点:并不是所有恶意行为都表现为高 CPU。有些脚本会伪装成系统进程名,比如叫[kworker]、systemd-log,如果不看真实路径,很难识别。
第二类是登录和账户层。攻击者可能通过密码爆破成功登录,或者通过某个被泄露的密钥直接用公钥登录。这类异常不一定占用 CPU,但会在登录日志里留下成功记录,在authorized_keys或/etc/passwd里留下痕迹。
第三类是网络层。服务器可能存在异常的外连请求,比如有进程持续向某个陌生 IP 发送数据;也可能正在被外部扫描或 DDoS 打流量。网络层问题未必能从top看到,必须同时看连接状态。
第四类是应用和数据库层。对于后端服务,一个“恶人”完全可能是一个恶意脚本在疯狂调用接口,导致服务线程耗尽;也可能是一条没有 WHERE 条件的 SQL,锁住整张表,让其他会话全部卡在Waiting for table metadata lock。这类问题表面上是“服务器很慢”,实际根源在应用线程池和数据库会话里。
1.2 从告警到定位的总体排查链路
无论异常最终出现在哪一层,都建议按下面这个顺序推进:先记录现场,再隔离影响,再溯源,再加固。不要在还没有弄清楚“恶人”是谁之前就乱杀进程或乱删文件,因为很多线索只有一次查看机会。
下面这张表可以作为整个排查过程的骨架:
| 现象 | 优先检查位置 | 核心命令 | 可能处置方式 |
|---|---|---|---|
| CPU 飙高 | 进程列表、/proc 信息 | top、ps aux、lsof -p PID | 终止异常进程,清理持久化 |
| 登录失败暴增 | 登录日志、lastb | grep Failed /var/log/secure、lastb | 封禁攻击源 IP,启用 fail2ban |
| 出现陌生成功登录 | wtmp、sshd 日志 | last、grep Accepted /var/log/secure | 检查账户和 authorized_keys,撤销风险密钥 |
| 存在异常外连 | 网络连接、进程端口 | ss -tnp、lsof -i | 追踪对应进程,终止并排查持久化 |
| 业务变慢、连接耗尽 | 数据库会话、应用线程 | SHOW FULL PROCESSLIST、sys.session | 清理长事务/慢查询,限制连接数 |
| 重启后异常复现 | 定时任务、服务、开机脚本 | crontab -l、systemctl list-units | 删除恶意定时任务和 systemd 服务 |
这个顺序不是固定的。如果告警明确指向数据库,可以直接跳到数据库层;如果日志里已经看到大量爆破记录,就应该先处理登录层。关键是不要只看单一维度。
这里还要强调一个原则:学习环境和生产环境的处置姿态不同。学习环境里可以大胆重启、重装、快速 kill;生产环境第一原则是保留现场证据,再做最小变更,降低对业务的影响。后面在第 5 节会专门对比这一差异。
2. 第一现场:CPU 飙高时,先在进程和内核日志里找证据
假设你现在已经通过监控平台或uptime确认系统负载非常高,接下来需要找到具体是哪个进程在消耗资源。
2.1 用 top 和 ps 找出高占用进程,但别只看第一屏
第一个命令基本都是top。执行后按P按 CPU 排序,按M按内存排序。超出一屏的结果无法在一个画面里完整展示,所以正确做法是使用批处理模式输出到文件,方便后续分析:
top -b -n 1 -o %CPU | head -n 30如果想看全部进程而不是只看 top 默认的前 20 行,可以用下面的形式:
ps aux --sort=-%cpu | head -n 30 ps aux --sort=-%mem | head -n 30看到异常进程后,把 PID、USER、%CPU、%MEM、COMMAND 这几列记录下来。判断一个进程“异常”并不只看 CPU 高:还要结合命令路径是否合理、启动用户是否符合预期、进程名是否存在混淆。比如进程名显示为-bash,但 PID 很小、启动时间很早,这可能就是一次可疑的交互登录,而不是正常的 bash 进程。
这里有一个很常见的坑:只用ps或top看一眼进程名就做判断。许多恶意进程会把自己的命令行伪装成系统常见名字,或者通过覆盖/proc/PID/comm来改名。所以下一步必须进入/proc目录查看更真实的运行状态。
2.2 从 /proc 看进程的真实身份
/proc目录是 Linux 暴露进程运行信息的虚拟文件系统,查看进程的 exe 软链接、当前工作目录、打开的文件、网络连接,比ps展示的信息更接近真实。
以 PID 为 12345 的进程为例:
ls -l /proc/12345/exe cat /proc/12345/cmdline | tr '\0' ' '; echo readlink /proc/12345/cwd cat /proc/12345/status | grep -E "Name|Pid|PPid|Uid|Gid"/proc/PID/exe指向进程可执行文件的真实路径。如果显示路径为临时目录、/tmp、/var/tmp或/dev/shm,几乎可以判定为高风险。/proc/PID/cmdline用\0分隔参数,转成空格后可以看清完整启动命令。/proc/PID/cwd显示当前工作目录,如果工作目录在可疑路径,也说明进程来源异常。/proc/PID/status可以查看进程的 UID/GID,确认运行身份是否合理。
查看进程打开的文件和网络连接,是判断进程是否在进行可疑读写或外连的关键:
lsof -p 12345 | head -n 20 ss -tnp | grep 12345lsof -p能列出该进程打开的文件、共享库、日志文件;ss -tnp能列出进程建立的 TCP 连接。如果发现进程持有大量可疑的文件句柄,或正在持续向某个陌生 IP 发起连接,就需要把网络层也纳入排查范围。
如果无法使用lsof,可以安装后再执行;也可以直接遍历/proc/PID/fd目录:
ls -l /proc/12345/fd | head -n 202.3 遇到异常进程后的处置顺序
确认进程可疑之后,第一反应是“kill 掉”,但更稳妥的处置顺序是先保留证据,再终止进程。
推荐做法是先把现场信息完整保存下来,例如:
# 保存进程信息和打开文件 ps aux | grep 12345 > /tmp/abnormal_process_info.txt ls -l /proc/12345/exe > /tmp/abnormal_process_exe.txt lsof -p 12345 > /tmp/abnormal_process_fd.txt ss -tnp | grep 12345 > /tmp/abnormal_process_net.txt # 如果怀疑是二进制文件,先不要删除,复制到隔离目录用于分析 cp -a /proc/12345/exe /tmp/sample_$(date +%F).bin 2>/dev/null || true保存完证据后,再使用kill终止进程。如果普通 kill 无法终止,可以考虑kill -9。但要注意,这里的“无法终止”可能是进程处于 D 状态(不可中断的 IO),也可能是被内核锁住;强行终止前建议先看/proc/PID/status中的进程状态。
第二个常见坑也随之出现:只 kill 进程而不清理持久化。恶意程序通常不会只以孤立进程存在,它往往会在crontab、/etc/cron.*、systemd 服务、~/.bashrc、~/.profile或/etc/rc.local里留下启动入口。如果不清除这些入口,进程被 kill 后会在一定时间后再次启动,出现“重启之后又回来了”的现象。
建议在杀进程之后立即检查:
crontab -l ls -la /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/ systemctl list-units --type=service --state=running | grep -vE "systemd|polkit|dbus|sshd|crond|network|rsyslog"配合启动脚本检查:
grep -nE "curl|wget|base64|/tmp/|/dev/shm" ~/.bashrc ~/.profile /etc/rc.local 2>/dev/null如果找到了可疑脚本,在确认内容前不要贸然删除,先把内容复制出来,再移除对应的 crontab 条目或 systemd 服务,最后重启验证是否还会出现。
第三类坑是“只看 CPU,不看网络外连”。恶意程序的真实危害往往不在于抢占 CPU,而在于它会持续外联把数据传出去。因此只要发现了异常进程,就必须同步用ss或lsof -i检查外连地址。否则即使杀掉了 CPU 高的进程,潜伏在其他名字下的通信进程仍然可能继续工作。
3. 追查“恶人”的入口:登录和认证日志
很多时候,异常进程只是结果,真正的入口在登录环节。攻击者能够投放恶意程序,通常意味着成功登录过服务器,或者获得了某个高权限账户的访问通道。因此要回答“恶人是怎么进来的”,接下来必须查登录记录。
3.1 找出今天谁成功登录过
Linux 下最直接的登录历史是last,它读取/var/log/wtmp,记录所有成功登录和重启历史:
last -n 30 last -u root -n 20第一行会显示最近登录的用户、来源 IP、登录时间。重点关注几个信息:来源 IP 是否属于你熟悉的办公出口、云安全组或堡垒机;登录时间是否在业务低谷或深夜;是否存在root直接从外网 IP 登录的记录。
同时查看 SSH 服务日志中Accepted记录。多数情况下,成功登录一定有Accepted publickey或Accepted password字样:
# CentOS/RHEL grep "Accepted" /var/log/secure | tail -n 30 # Ubuntu/Debian grep "Accepted" /var/log/auth.log | tail -n 30 # 使用 systemd 日志时 journalctl -u sshd --since "today" | grep Accepted如果发现成功登录的来源 IP 异常,需要继续确认这个会话当前是否还存活。可以通过who或ss -tnp查看当前的 SSH 会话:
who如果看到异常来源 IP 仍保持连接,可以考虑断开该会话。通过pkill -u username可以踢掉某个用户的所有会话,但这种操作要谨慎,可能影响其他正常登录。更精确的做法是先用tty信息定位,再只结束对应会话进程。
3.2 密码爆破的痕迹怎么看
大量Failed password是攻击者正在爆破 SSH 密码的典型现象。查看失败登录最多的来源 IP 可以这样做:
# CentOS/RHEL grep "Failed password" /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr | head -n 20 # Ubuntu/Debian grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr | head -n 20awk '{print $(NF-3)}'提取的是日志行末尾倒数第四个字段,通常就是来源 IP。之后可以继续查看这些 IP 是否有过成功登录:
grep "Accepted" /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr | head -n 20lastb读取/var/log/btmp,显示失败的登录尝试,适合快速判断爆破账号:
lastb | head -n 30如果大量失败尝试来自少数几个 IP,临时封禁这些 IP 是必要的。但下一节会提到,封 IP 前要先确认不是自己的 NAT 出口或云平台健康检查。
3.3 检查 authorized_keys 和用户账户
密码爆破属于外部入口,还有一种更隐蔽的入口:攻击者可能已经拿到了某个用户的私钥,或者往authorized_keys里写入了自己的公钥。这类后门登录不使用密码,不容易在Failed password里看到,所以必须主动检查。
全盘查找所有authorized_keys文件和known_hosts文件:
find / -name "authorized_keys" -type f 2>/dev/null find / -name "authorized_keys2" -type f 2>/dev/null逐个查看找到的文件,确认每一行公钥是否真的是你能识别的密钥。如果存在不明公钥,在备份后移除对应行。同时检查系统里是否存在异常用户:
# 查看 UID=0 的高权限用户,以及可登录 shell 的非 root 用户 getent passwd | awk -F: '$3==0 || ($1!="root" && $7 ~ /(bash|sh)$/) {print}'正常情况下,UID 为 0 的只有 root;若出现其他 UID=0 用户,风险极高。非 root 用户即使有 bash shell 也不一定异常,需要结合创建时间和最近登录记录判断。
检查完账户后,不要忽略 shell 初始化文件。攻击者为了在用户登录后自动执行恶意代码,常把内容写入~/.bashrc、~/.profile或/etc/profile.d/:
cat ~/.bashrc | grep -nE "curl|wget|base64|nohup|/tmp|/dev/shm" grep -rnE "curl|wget|base64|nohup|/tmp|/dev/shm" /etc/profile.d/ 2>/dev/null这一层排查的目标不是抓到某一个 IP,而是确认后续清理之后,攻击者没有保留重新进入服务器的入口。
4. “恶人”还可能躲在数据库里:阻塞和异常会话
除了操作系统层面的进程和登录,数据库里的“恶人”也经常会让服务器看起来像是“被追杀”。表现是应用接口大面积超时,数据库 CPU 或连接数飙升,应用日志里不断出现连接获取超时。很多人的第一反应是重启应用,但如果根因是数据库里的某个会话锁住了关键行,重启应用往往没有效果。
4.1 数据库“恶人”指什么
数据库层面的异常主体主要有三类:长时间不释放连接的 Sleep 会话、把某张表或某些行锁住的 DML 会话,以及完全没有索引或 WHERE 条件缺失的大查询。
以 MySQL 为例,最直观的查询是SHOW PROCESSLIST:
SHOW FULL PROCESSLIST;Id是会话 ID,对应后续KILL操作的标识;Command显示会话正在干什么,常见值包括Query、Sleep、Locked;Time表示会话已经在该状态持续的时间,秒为单位;State给出更细的执行状态,例如Sending data、Waiting for table metadata lock、Waiting for lock;Info显示正在执行的 SQL,有时能看到完整 SQL,有时只有前 100 个字符。
判断一个会话是否是“恶人”,不能只看Command = Sleep。短时间的 Sleep 是连接池的正常状态,但几百秒甚至上千秒的 Sleep 就可能是在连接建立后长时间不操作,占用了连接数上限。
4.2 用 SHOW PROCESSLIST 和 sys.session 定位
SHOW FULL PROCESSLIST是最基础的方式,但输出比较长。MySQL 5.7 之后的版本提供了sys.session视图,查询更灵活:
-- 查看正在执行非 Sleep 命令的会话,按耗时倒序 SELECT * FROM sys.session WHERE command <> 'Sleep' ORDER BY time DESC; -- 查看长时间 Sleep 的会话 SELECT * FROM sys.session WHERE command = 'Sleep' AND time > 100 ORDER BY time DESC;sys.session里的conn_id对外对应SHOW PROCESSLIST的Id,current_statement对应正在执行的 SQL。如果能看到command = 'Query'、time达到几十秒甚至上百秒,而它执行的是一条没有 WHERE 条件的DELETE或UPDATE,基本可以判定就是它阻塞了其他会话。
进一步确认锁等待关系,可以查询:
SELECT * FROM sys.innodb_lock_waits;sys.innodb_lock_waits会输出阻塞者与被阻塞者之间的关系字段,例如blocking_pid和waiting_pid。在确认阻塞者之后,需要谨慎处理:先通过SHOW PROCESSLIST的Info或sys.session的current_statement确认这条 SQL 是什么,是否有业务在跑,再决定是否终止。
终止一个数据库会话用KILL:
KILL 12345;如果 KILL 后会话仍不释放,可能是元数据锁等待,常见原因是有其他长事务持有表结构引用。此时要先把持有引用的事务也找出来,否则 KILL 掉当前会话后,新的 DDL 或 DML 仍可能继续等待。
这里必须提醒:不要在未确认会话对应的业务影响之前,直接对大查询执行 KILL。对于线上数据库,一个被 KILL 的长事务可能导致部分业务数据未提交、应用侧抛出异常。如果影响较大,应先在维护窗口或业务低峰处理,并且确保应用有连接失败重试机制。
4.3 阻止疯狂会话的配置建议
清理完当前“恶人”,还需要从配置上减少后续风险。MySQL 常见相关参数如下:
| 参数 | 默认值(常见版本) | 作用 | 配置过小的影响 | 配置过大的影响 |
|---|---|---|---|---|
max_connections | 151 | 限制最大并发连接数 | 正常业务无法建连 | 高并发下 DB 负载失控 |
max_user_connections | 0(不限) | 限制单用户并发连接数 | 部分客户端被拒绝 | 占用太多了,仍可能拖垮实例 |
wait_timeout | 28800 | 非交互连接空闲超时 | 连接频繁被断开 | Sleep 会话占用连接不释放 |
interactive_timeout | 28800 | 交互连接空闲超时 | mysql 客户端操作易断 | 交互连接长期挂起 |
lock_wait_timeout | 31536000(某些版本) | 等待锁超时时间 | 业务快速失败但可能报错 | 锁等待时间过长,线程堆积 |
long_query_time | 10 | 记录超过该秒数的查询到慢查询日志 | 日志量变大 | 慢查询被漏掉 |
max_execution_time | 0(不限) | SELECT 最大执行时间 | 长查询被中断 | 大查询占资源,拖垮实例 |
生产环境调整这些参数要平滑,不能通过重启数据库这种粗暴方式验证。wait_timeout、interactive_timeout可以通过SET GLOBAL动态修改,但新值只对新连接生效;max_connections可以通过SET GLOBAL max_connections = 200;临时调整,同时要注意监控连接数趋势。真正持久化还需要修改配置文件,并在维护窗口逐步重启。
对于大查询,还可以在应用侧增加超时和限流。例如 MySQL 8.0 可通过SET_VAR或会话变量设置max_execution_time,但根本上还是需要代码审查,确保SELECT、DELETE、UPDATE都带有合理索引和必要的 WHERE 条件。
5. 从排查到处置:封禁、清理与加固
当你已经定位了恶人的来源、异常进程和入口,下一步就是处置。处置不是一个动作,而是一组动作,包括封禁攻击源、清理持久化、加固 SSH、修复被篡改的账户和配置文件。
5.1 临时封禁攻击源 IP,还要考虑误封
对于从日志里确认的攻击源 IP,可以先做临时封禁。这里要非常小心一个误封问题:很多公司出口是 NAT 的,多个办公区可能共享少量公网 IP;云平台健康检查、监控探针、堡垒机也可能来自固定 IP。封禁前至少要做三件事:
- 确认该 IP 没有出现在最近的成功登录记录里;
- 确认该 IP 不在安全组或白名单配置中;
- 确认该 IP 不是你自己当前办公出口。
CentOS/RHEL 7 及以上使用 firewalld 时,可以通过富规则封禁:
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=1.2.3.4 reject' firewall-cmd --reload firewall-cmd --list-all如果系统使用 iptables 服务:
iptables -I INPUT -s 1.2.3.4 -j DROP service iptables save iptables -L INPUT -n临时封禁可以快速降低服务器被继续爆破的压力,但它不能持久阻止有代理池的攻击者。更系统的方法是部署 fail2ban。它的工作方式是监控日志文件,发现多次失败后自动调用防火墙封禁来源 IP,并在一段时间后自动解封。
# /etc/fail2ban/jail.local [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/secure maxretry = 5 bantime = 3600配置完成后执行:
systemctl enable --now fail2ban fail2ban-client status sshdfail2ban-client status sshd会显示当前被封禁的 IP 列表和封禁次数。这种方式的优点是自动、可解封,误封影响比手动永久封禁小;缺点是对分布式低频爆破不敏感,所以不能只依赖它。
需要特别说明:封 IP 是止血,不是根治。如果攻击者已经从这份服务器里拿到了账号或密钥,封掉当前 IP 只是暂时让他换一台机器接着打。真正需要的是在第 5.3 节中把 SSH 入口收紧。
5.2 清理定时任务、系统服务和可疑文件
之前第 2 节提到只 kill 进程不清理持久化,问题会复发。实际清理时要同时检查以下几处:
- 当前用户的 crontab:
crontab -l - 系统定时任务:
/etc/crontab、/etc/cron.d/、/etc/cron.hourly/、/etc/cron.daily/、/etc/cron.weekly/、/etc/cron.monthly/ - systemd 定时器:
systemctl list-timers - systemd 服务:
systemctl list-units --type=service --state=running - 启动脚本:
/etc/rc.local、/etc/rc.d/rc.local - 用户级初始化文件:
~/.bashrc、~/.profile、~/.bash_profile
发现可疑项后,先复制一份内容留档,再删除或注释对应条目。示例:
# 查看某个具体 cron 文件内容 cat /etc/cron.d/evil_task # 删除可疑 cron 任务,操作前确认内容 crontab -r rm -f /etc/cron.d/evil_task删除 systemd 服务时,需要先停止再移除:
systemctl stop evil.service systemctl disable evil.service rm -f /etc/systemd/system/evil.service systemctl daemon-reload清理可疑文件时,不要随手删除。相对稳妥的做法是先移动到隔离目录:
mkdir -p /tmp/quarantine mv /tmp/evil_script.sh /tmp/quarantine/这样如果后续需要分析样本或确认文件来源,仍然有地方可查。如果确认无用,再彻底删除。
同时要留意日志里被人为清理的空白期。如果某段时间的登录日志里完全没有记录,但last显示在这段时间存在登录会话,那可能是攻击者清理过日志。这类情况通常意味着入侵痕迹已经被故意抹除,处理优先级要高于普通爆破。
5.3 SSH 加固清单
清理完入口和持久化之后,必须把 SSH 配置收紧,否则服务器依然暴露在公网上,只是暂时没有被打而已。下面是一组常见但有效的加固方式:
# /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes MaxAuthTries 3 LoginGraceTime 30 AllowUsers ops deployPermitRootLogin no:禁止 root 直接远程登录,日常操作先登录普通用户再su切换。PasswordAuthentication no:在纯密钥环境下可以关闭密码登录,能极大降低爆破成功概率。但前提是已经确认至少有一台机器能通过密钥正常登录,否则一旦配置错误,会导致自己被关在门外。MaxAuthTries 3:限制单次连接尝试次数,爆破脚本的低效会明显下降。LoginGraceTime 30:限制登录超时,避免建立连接后长时间不认证。AllowUsers:明确指定允许登录的用户名单,效果比“禁止某些用户”更稳。PermitRootLogin和PasswordAuthentication这两项改动较大,一定先开着新会话验证密钥登录可用,再重启 sshd。
修改完成后,先执行语法检查:
sshd -t再平滑重启 SSH 服务:
systemctl reload sshd注意不要轻易使用systemctl restart sshd,reload对已连接会话影响更小。
“修改默认端口”也是一个常见操作,但它的本质是降低自动化扫描的命中率,不能从安全模型上解决问题。修改端口前需要评估业务防火墙、云安全组、监控脚本是否有依赖 22 端口的地方,否则会导致部分服务无法连接。
5.4 学习环境与生产环境的处置差异
整个处置流程里,最容易造成事故的往往不是“处置不了”,而是在生产环境里用了学习环境那种大刀阔斧的手法。两者的差异用一张表可以看得很清楚:
| 环节 | 学习/测试环境 | 生产环境 |
|---|---|---|
| 发现可疑进程 | 可直接 kill,通常无业务影响 | 先确认进程归属,保留现场,按变更流程处置 |
| 修改 sshd_config | 可大胆测试,失败后重装即可 | 必须先验证密钥可用,再reload,并保留一个可回滚窗口 |
| 封禁 IP | 误封影响小 | 先排除办公出口、堡垒机、健康检查 IP |
| 清理文件 | 直接删除即可 | 先移到隔离目录,记录 md5、路径、源信息 |
| 重启服务 | 直接systemctl restart | 优先reload,高并发业务需评估连接中断影响 |
| 数据库 KILL 会话 | 基本可以直接杀 | 先确认事务影响,业务低峰处理,观察应用日志 |
| 样本分析 | 随意分析 | 复制后离线分析,不要在生产机直接执行可疑文件 |
生产环境还有一个不可省略的步骤:出了问题之后,如果判断是安全事件,应保留日志和样本至少一段时间,并复盘攻击路径。很多团队会忽略这个环节,结果下次被同样方式入侵时,又从头开始找日志。
6. 常见问题排查速查表和加固检查清单
最后给出一份可以直接复用的排查速查表和加固检查清单。平时可以把它们贴在运维文档或告警响应文档里。
6.1 现象对应排查命令速查
| 现象 | 检查命令 | 常见原因 | 处置建议 |
|---|---|---|---|
| CPU 持续 100%,进程名随机 | top -b -n 1 -o %CPU、ls -l /proc/PID/exe | 挖矿或恶意脚本 | 保留证据后终止进程,清理 cron/systemd 持久化 |
| 大量 Failed password | grep "Failed password" /var/log/secure | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr | SSH 爆破 | 封禁 IP,启用 fail2ban,考虑关闭密码登录 |
| 出现陌生 IP 成功登录 | last、grep Accepted /var/log/secure | 密钥泄露或弱密码登录成功 | 撤销对应密钥,改密码,检查 authorized_keys |
| 端口被占用/异常外连 | ss -tnp、lsof -i | 恶意进程外联 | 定位进程,终止并清理持久化 |
| 定时任务异常 | crontab -l、ls /etc/cron.d/、systemctl list-timers | 被写入持久化任务 | 先备份再删除,检查 systemd 服务 |
| 数据库连接耗尽,业务超时 | SHOW FULL PROCESSLIST、SELECT * FROM sys.session | 长事务、慢查询、无索引 | 确认后 KILL 异常会话,优化 SQL,限制连接数 |
| 应用重启后异常复现 | 重点检查~/.bashrc、/etc/rc.local、systemd 服务 | 持久化后门未清理 | 清理脚本文件与开机启动项 |
| 某用户存在不明公钥 | find / -name authorized_keys -type f | 攻击者写入了自己的公钥 | 备份后移除不明公钥,复查账户和 shell |
6.2 验证加固是否生效的最小检查清单
做完一轮处置之后,不要直接认为“好了”。建议按下面的清单再走一遍,确认清理和加固确实生效。
- [ ]
crontab -l已经是空的,或者只保留了业务需要的任务。 - [ ]
systemctl list-units --type=service --state=running中没有陌生服务。 - [ ]
last最近的成功登录记录里没有未知 IP。 - [ ] 所有
authorized_keys文件都能追溯到已知团队成员。 - [ ]
/etc/ssh/sshd_config关键参数已生效,sshd -t没有报错。 - [ ] 用一台干净的机器测试新的密钥登录,确认还能正常进入服务器。
- [ ] 如果开启了 fail2ban,
fail2ban-client status sshd能看到已监控的 jail。 - [ ] 数据库
SHOW FULL PROCESSLIST里没有长时间 Sleep 或 Locked 会话。 - [ ] 监控平台上 CPU、内存、连接数回到基线值,并且持续观察至少一个完整业务周期。
- [ ] 对外防火墙和安全组只放行必要的端口,其余端口默认拒绝。
6.3 下一步:把“狩猎”变成日常监控
“追杀这个服务器的恶人”不能永远靠人工处理。真正稳定的服务器,应该把“找恶人”变成自动化的一部分:进程和端口监控、登录日志集中采集、失败次数阈值告警、数据库慢查询和连接数监控,这些都应该在日常就位。否则等到告警爆发时,就从“一个普通的排查任务”升级成了一次安全事件。
对于新人,建议先在测试服务器上模拟一遍整个流程:开启 SSH 密码登录,用错误密码尝试连接,观察secure日志和lastb;再手动写一个耗 CPU 的脚本放在后台,练习用/proc/PID定位并清理。练习过程中不要只记命令,关键是理解每个命令对应的是哪一种“恶人”藏匿方式。这样真正遇到问题时,才能从一条告警一路追到完整证据链。