构建自动化SSH攻击IP动态黑名单:从日志分析到防火墙集成实战

📅 2026/8/14 6:11:15
构建自动化SSH攻击IP动态黑名单:从日志分析到防火墙集成实战
1. 项目缘起为什么需要一个动态更新的SSH攻击IP列表做运维或者自己搭过服务器的朋友对SSH端口上那些永不停歇的“敲门声”应该都不陌生。我的几台公网服务器几乎每天都能在/var/log/auth.log或者/var/log/secure里看到成百上千条来自全球各地IP的登录尝试。这些尝试大多使用root、admin、user等常见用户名配合弱密码字典进行暴力破解。虽然服务器本身设置了强密码、密钥登录甚至Fail2ban这类防护工具但看着日志里这些无效的尝试记录心里总是不太踏实。更让人头疼的是攻击源并非一成不变。今天可能是某个数据中心的一整段IP在扫描明天就换成了另一个国家的家用宽带IP。传统的静态IP黑名单维护起来非常低效往往你刚加进去一批攻击者已经换了一批新的IP卷土重来。于是我开始思考能不能有一个机制自动地、持续地收集这些正在对我以及可能对其他管理员发动SSH暴力破解的IP地址并形成一个可以共享和应用的动态黑名单这就是“SSH攻击IP列表【不定时更新】”这个项目的初衷。这个列表的核心价值不在于提供一个一劳永逸的“终极防火墙”因为互联网上的恶意IP是海量且动态变化的。它的价值在于提供一个基于实时攻击行为的、高置信度的威胁情报源。你可以把它看作是一个“众包”的威胁感知系统通过分析真实的服务器访问日志筛选出那些行为特征明显的恶意IP并将它们汇总起来。其他管理员拿到这个列表后可以将其作为自己安全策略的一个有力补充在攻击发生初期甚至发生之前就提前进行阻断从而提升服务器的整体安全水位。2. 攻击日志分析如何从海量日志中精准识别恶意IP不是所有连接失败的SSH尝试都是攻击。可能有用户输错了密码可能有自动化脚本配置错误也可能有网络波动导致的连接中断。因此制定一套清晰、可量化的识别规则至关重要。如果规则太松会混入大量误报导致正常用户被误封如果规则太严又会漏掉很多狡猾的、低频次的攻击。经过一段时间的实践和调整我总结出以下几个核心判定维度它们共同构成了筛选恶意IP的“过滤器”。2.1 基于失败频率与时间窗口的判定这是最直接也最有效的判断依据。一个正常的用户在短时间内连续输错密码的次数是有限的。我的基础规则是在10分钟的时间窗口内来自同一个IP地址的SSH登录失败次数超过5次则将该IP标记为可疑攻击源。这个规则是如何在日志中体现的呢我们来看一条典型的SSH失败日志以常见的Ubuntu/Debian系统为例May 10 14:23:12 my-server sshd[12345]: Failed password for invalid user admin from 203.0.113.100 port 54321 ssh2 May 10 14:23:15 my-server sshd[12346]: Failed password for root from 203.0.113.100 port 54322 ssh2 May 10 14:23:18 my-server sshd[12347]: Failed password for invalid user test from 203.0.113.100 port 54323 ssh2可以看到IP203.0.113.100在短短6秒内尝试了admin、root、test三个不同用户并且都失败了。这种行为模式与正常用户登录失败比如忘记密码后尝试一两次有本质区别它表现出明显的自动化、枚举式攻击特征。在具体实现时我会使用awk、grep配合一些时间窗口工具如logrotate配合自定义脚本或者更专业的日志分析工具如lnav来统计。一个简单的命令行统计示例# 统计过去1小时内每个IP的失败次数并按次数降序排列 grep Failed password /var/log/auth.log | grep date -d 1 hour ago %b %d %H: | awk {print $(NF-3)} | sort | uniq -c | sort -nr这条命令会输出类似这样的结果12 203.0.113.100 8 198.51.100.200 1 192.0.2.55其中203.0.113.100在1小时内失败了12次远高于阈值可以确认为攻击IP。2.2 基于攻击行为模式的深度识别仅仅看失败次数还不够。有些高级攻击者会刻意控制尝试频率使其低于我们设定的阈值进行“慢速爆破”。这就需要结合更多的行为模式来分析。用户名枚举观察一个IP是否在尝试大量不同的用户名。正常的用户登录用户名通常是固定的1个或少数几个。而攻击日志中经常出现root,admin,user,test,oracle,ftp等数十甚至上百个用户名的尝试记录。如果一个IP在尝试了root失败后紧接着又尝试admin、ubuntu、pi树莓派默认用户等这几乎可以肯定是恶意扫描。协议与端口异常虽然我们主要关注SSH默认22端口但有些扫描器会同时探测其他端口如Telnet23、FTP21等。如果在同一时间段内从同一个IP发往服务器不同端口的连接都失败了这也能侧面印证其恶意性。可以通过关联分析不同服务的日志如/var/log/secure中的SSH日志和/var/log/vsftpd.log中的FTP日志来发现这类关联攻击。地理与ISP信息虽然不能以偏概全但长期观察会发现大量攻击流量来源于某些特定的数据中心IP段或国家的宽带出口。使用whois或geoiplookup等工具查询IP信息可以作为辅助判断。例如如果一个IP来自某个已知的、频繁用于发起攻击的云服务商VPS网段即使其当前尝试次数不高也需要提高警惕。2.3 误报过滤与白名单机制任何自动化系统都必须考虑误报。以下情况需要特别注意并设置白名单自身管理IP你办公室、家里的公网IP或者你使用的跳板机、CI/CD服务器的IP。必须无条件加入白名单否则你会把自己锁在外面。合作伙伴或API调用IP如果有其他系统通过SSH与你的服务器交互虽然不推荐但可能存在也需要加入白名单。短暂高失败率的合法场景例如团队新成员第一次配置SSH密钥时可能操作失误导致多次连接失败。这种情况需要人工介入判断。我的做法是维护一个独立的trusted_ips.txt文件在运行攻击IP检测脚本时首先排除这个列表中的IP。这个文件需要严格管理只有确认为绝对可信的IP才能加入。3. 自动化收集系统的构建与实践手动分析日志效率太低必须实现自动化。我的目标是构建一个轻量级、可扩展的自动化系统它能够定时分析日志、提取恶意IP、去重、格式化并最终生成一个易于使用的IP列表文件。整个系统的核心是一个Shell脚本配合Linux的cron定时任务来运行。3.1 核心脚本设计与实现下面是我在用的一个简化版核心脚本ssh_attack_collector.sh的框架和关键部分。它包含了日志分析、IP提取、去重、白名单过滤和结果输出等完整流程。#!/bin/bash # 配置文件路径 LOG_FILE/var/log/auth.log TRUSTED_IPS_FILE/path/to/trusted_ips.txt OUTPUT_FILE/path/to/ssh_attack_ips.txt TEMP_FILE/tmp/attack_ips.tmp # 定义时间窗口和阈值例如分析过去2小时失败次数3 TIME_WINDOW2 hours ago FAILURE_THRESHOLD3 # 1. 获取时间窗口起始点的字符串表示用于grep # 这里使用了一种兼容性较好的方法生成如 “May 10 12:” 的格式 START_TIME$(date -d $TIME_WINDOW %b %d %H:) # 2. 从日志中提取失败记录并统计IP出现次数 echo 正在分析 $LOG_FILE 中 $START_TIME 之后的日志... grep Failed password $LOG_FILE | grep $START_TIME | awk {print $(NF-3)} | sort | uniq -c $TEMP_FILE # 3. 过滤出超过阈值的IP echo 正在筛选失败次数超过 $FAILURE_THRESHOLD 次的IP... ${TEMP_FILE}2 # 清空临时文件2 while read -r count ip; do if [[ $count -gt $FAILURE_THRESHOLD ]]; then # 4. 白名单过滤 if ! grep -q ^$ip$ $TRUSTED_IPS_FILE 2/dev/null; then echo $ip # $count次失败$(date) ${TEMP_FILE}2 else echo 信息可信IP $ip 已被过滤失败 $count 次 fi fi done $TEMP_FILE # 5. 与现有列表合并、去重、排序 if [ -f $OUTPUT_FILE ]; then cat $OUTPUT_FILE ${TEMP_FILE}2 | sort -u -t -k1,1 ${TEMP_FILE}3 mv ${TEMP_FILE}3 $OUTPUT_FILE else sort -u ${TEMP_FILE}2 $OUTPUT_FILE fi # 6. 清理与报告 current_count$(wc -l $OUTPUT_FILE) new_ips_count$(wc -l ${TEMP_FILE}2) echo 处理完成。当前攻击IP列表共有 $current_count 个IP本次新增 $new_ips_count 个。 rm -f $TEMP_FILE ${TEMP_FILE}2 # 7. 可选将列表转换为防火墙规则例如iptables # echo 正在更新iptables规则... # while read -r line; do # ip$(echo $line | awk {print $1}) # # 检查规则是否已存在避免重复添加 # if ! iptables -C INPUT -s $ip -j DROP 2/dev/null; then # iptables -A INPUT -s $ip -j DROP # echo 已屏蔽IP: $ip # fi # done $OUTPUT_FILE脚本关键点解析时间处理date -d “2 hours ago” ‘%b %d %H:’这个命令生成的格式如May 10 12:可以直接用于grep匹配日志时间戳的前半部分效率比解析完整时间戳高得多。IP提取awk ‘{print $(NF-3)}’是一个巧妙的用法。在Failed password日志行中IP地址通常是倒数第四个字段NF代表字段总数。这个写法比写死字段位置更健壮。白名单过滤grep -q “^$ip$”中的^和$确保了精确匹配整个IP地址避免部分匹配导致错误过滤。去重与合并sort -u -t’ ‘ -k1,1是关键。-t’ ‘指定空格为分隔符-k1,1表示只对第一列IP地址进行唯一性排序。这样即使同一IP后面的注释失败次数和日期不同也会被去重。防火墙规则集成脚本最后注释掉的部分展示了如何将IP列表动态应用到iptables防火墙。使用iptables -C先检查规则是否存在可以避免规则重复添加导致的问题。在实际生产环境中应用防火墙规则需要非常谨慎建议先在测试环境验证并确保有可靠的管理通道如通过未被封锁的IP或控制台可以恢复。3.2 定时任务与日志轮转的配合为了让这个收集系统持续运行需要配置cron定时任务。我设置为每小时运行一次这个频率在及时性和系统负载之间取得了较好的平衡。# 编辑当前用户的crontab crontab -e # 添加以下行表示每小时的第5分钟运行一次脚本 5 * * * * /bin/bash /path/to/ssh_attack_collector.sh /var/log/attack_collector.log 21同时必须考虑系统日志的轮转logrotate。像/var/log/auth.log这样的文件通常会被logrotate按天或按大小进行切割和压缩。如果我们的脚本只读取当前日志文件就会丢失历史数据。有几种应对策略分析多个日志文件修改脚本使其同时读取auth.log、auth.log.1、auth.log.2.gz等。可以使用find命令配合zcat来处理压缩文件。使用journalctlSystemd系统对于使用Systemd的系统更推荐使用journalctl命令来查询日志它能自动处理所有的日志存储和轮转。# 使用journalctl获取过去2小时内的SSH失败日志 journalctl --since “2 hours ago” -u ssh | grep “Failed password” | awk ‘{print $(NF-3)}’这种方式更简洁也是未来的趋势。3.3 列表的格式与维护生成的ssh_attack_ips.txt文件格式保持简洁明了203.0.113.100 # 12次失败Fri May 10 15:30:01 UTC 2024 198.51.100.200 # 8次失败Fri May 10 15:30:01 UTC 2024 192.0.2.0/24 # 整个网段扫描Fri May 10 16:45:22 UTC 2024每行一个IP或网段后面用#注释说明首次被识别时的失败次数和发现时间。这种格式既方便人阅读也便于其他脚本解析只需提取第一列。列表的维护同样重要。IP地址是有限的资源恶意攻击者可能会更换IP或者某些IP被其ISP清退后分配给正常用户。因此一个“永久”的黑名单是不科学的。我的策略是引入**TTL生存时间**机制。我修改了脚本在输出IP的同时会记录一个时间戳。另一个清理脚本会定期比如每周运行检查列表中的IP如果某个IP的记录时间超过设定的TTL例如30天并且近期如最近7天的日志中没有再发现它的攻击行为就将其从列表中移除。这样可以确保列表始终保持“新鲜”避免误封已经“从良”的IP地址。4. 攻击IP列表的应用场景与防御集成收集到列表只是第一步如何将它转化为实际的安全防御能力才是关键。下面介绍几种我实践过的、非常有效的集成应用方式。4.1 集成到防火墙进行实时屏蔽这是最直接、最有效的应用方式。将攻击IP列表动态加载到服务器的防火墙规则中实现实时阻断。对于使用 iptables 的系统可以像前面脚本示例中那样使用循环添加DROP规则。但更高效、更专业的方式是使用ipset。ipset是iptables的一个扩展它允许你将大量的IP地址放入一个集合中然后iptables只需一条规则匹配整个集合极大提升了规则匹配效率和可管理性。# 1. 创建一个名为“ssh_attackers”的ipset集合类型为hash:net支持IP和网段 ipset create ssh_attackers hash:net timeout 2592000 # timeout单位是秒这里设为30天 # 2. 在iptables中创建一条规则引用这个ipset iptables -I INPUT -m set --match-set ssh_attackers src -j DROP # 3. 编写脚本将攻击IP列表文件中的IP添加到ipset中 while read -r line; do ip$(echo $line | awk ‘{print $1}’) # 检查IP是否已在集合中避免重复添加ipset add命令本身会去重但检查一下更清晰 if ! ipset test ssh_attackers $ip 2/dev/null; then ipset add ssh_attackers $ip echo “已添加IP到屏蔽集合: $ip” fi done /path/to/ssh_attack_ips.txt使用ipset的优势非常明显即使列表中有上万个IP防火墙的规则链也依然简洁性能影响微乎其微。timeout参数可以自动清理过期IP与我们设想的TTL机制完美契合。对于使用 firewalld如CentOS/RHEL 7的系统Firewalld也支持ipset但配置方式略有不同需要通过firewall-cmd命令来管理。对于云服务器如AWS、GCP、阿里云各大云平台都提供了“安全组”或“防火墙规则”功能。你可以编写一个脚本定期调用云平台的API将攻击IP列表更新到安全组的“拒绝访问”规则中。这种方式是在网络入口处进行屏蔽不消耗服务器自身的CPU资源是最优解。但需要注意云平台安全组的规则数量限制。4.2 与Fail2ban联动增强防护Fail2ban本身就是一个通过分析日志动态封禁IP的工具。我们的攻击IP列表可以作为Fail2ban的一个补充数据源或前置过滤器。一种联动思路是将我们的攻击IP列表配置为Fail2ban的“永久封禁”列表banaction iptables-allports配合一个极长的bantime。这样这些IP在尝试连接的第一时间就会被Fail2ban封禁而无需再触发Fail2ban自身的检测规则如多次密码失败。这相当于为Fail2ban提供了一个“已知恶意IP情报库”使其防御更具前瞻性。具体做法是在Fail2ban的jail.local配置文件中为[sshd]或其他jail添加ignoreip的反向应用或者更直接地在Fail2ban的action配置文件中添加一个在启动时就加载该列表到防火墙的预处理动作。4.3 在边缘网络或WAF上应用如果你的服务器前方有反向代理如Nginx、负载均衡器或Web应用防火墙WAF可以在这些边缘节点上应用IP黑名单将攻击流量在到达业务服务器之前就拦截掉。这能最大程度地减轻后端服务器的压力。Nginx可以在http或stream用于TCP/UDP如SSH模块中使用deny指令。# 在 nginx.conf 的 http 或 stream 上下文中 include /path/to/ssh_attack_ips_nginx.conf;ssh_attack_ips_nginx.conf文件内容格式为deny 203.0.113.100; deny 198.51.100.200; deny 192.0.2.0/24;注意Nginx作为TCP代理处理SSH流量stream模块需要相应模块支持且配置更复杂。更常见的做法是在Nginx层面防护HTTP/HTTPS攻击。Cloudflare等CDN/WAF服务这些服务通常提供API允许你批量添加IP到防火墙黑名单中。你可以定期运行脚本将收集到的攻击IP通过API同步到Cloudflare的防火墙规则中实现全球网络层面的屏蔽。4.4 分享与协同防御个人收集的IP列表覆盖面有限。如果有多位管理员共享各自的攻击IP列表就能形成一个更全面、更及时的威胁情报网络。这就是“众包安全”的雏形。你可以将生成的ssh_attack_ips.txt文件放在一个安全的、可访问的位置如私有Git仓库、带认证的静态文件服务器并告知其他可信的同行。他们可以定期拉取这个列表并集成到自己的防御体系中。同样你也可以订阅他人维护的类似列表。重要安全提示在分享IP列表时务必脱敏。确保文件中不包含任何可能泄露你服务器身份的信息如你的真实服务器IP、主机名、特定用户名等。只分享纯粹的IP地址和必要的注释如首次发现时间。同时建立一定的信任机制避免恶意列表污染。5. 实践中的挑战、优化与高级技巧在运行这个系统的过程中我遇到了不少坑也总结出一些优化方案和高级技巧。5.1 应对攻击者的规避策略攻击者不是静止的他们会采取各种手段来绕过基于IP的封禁。IP地址欺骗与快速更换这是最常见的。攻击脚本使用代理池、被入侵的“肉鸡”僵尸主机或云服务商快速发放的弹性IP使得攻击源IP变化极快。应对策略是引入网段封禁。如果发现来自同一个/24C段甚至/16B段的多个IP都在进行攻击很可能是同一个攻击者控制的IP池。此时可以考虑封禁整个网段。我们的脚本需要增强对IP地址的聚合分析能力识别出这种“IP簇”攻击模式。但网段封禁要格外谨慎避免误伤大量正常用户。慢速扫描与低频率攻击将攻击频率降低到我们设定的阈值以下。应对方法是延长分析时间窗口。例如将统计周期从“2小时内失败5次”调整为“24小时内失败10次”。同时结合“用户名枚举”等行为模式特征即使频率低但行为异常也应纳入监控。分布式攻击DDoS式爆破由成千上万个不同的IP同时发起低频次尝试。单个IP的行为看起来不明显但聚合起来总量巨大。应对这种攻击基于IP的列表效果有限需要更高级的防护如速率限制在防火墙或应用层对到SSH端口的连接速率进行全局限制。挑战-响应机制如使用fail2ban的recidive累犯过滤器追踪那些虽然单次攻击不强但长期持续出现的IP。端口敲门隐藏SSH端口只有按特定顺序访问一组“敲门端口”后真正的SSH端口才会临时开放。5.2 系统性能与可靠性的优化当服务器访问量很大或者攻击非常频繁时日志分析脚本可能成为负担。使用更高效的工具对于海量日志awk和grep可能稍慢。可以考虑使用logwatch、goaccess主要用于Web等专业的日志分析工具或者使用Python/Go编写更高效的分析程序利用多线程或异步IO处理。增量分析不要每次都从头读取整个日志文件。可以记录上次分析到的日志文件位置行号或时间戳下次只分析新增的部分。这可以通过tail -F跟踪模式或者记录journalctl的游标来实现。避免列表无限膨胀如前所述TTL机制至关重要。定期清理老旧IP保持列表在可控范围内例如几千条这对防火墙性能和内存占用都有好处。设置熔断机制在脚本中如果检测到短时间内新增的攻击IP数量异常暴涨例如一分钟内新增上千个可能是遇到了大规模扫描或脚本自身出现了逻辑错误。此时脚本应该发出严重警报并暂停自动添加IP等待人工审核防止因误判导致大规模误封。5.3 从攻击日志中挖掘更多价值攻击日志不仅是需要清除的“噪音”更是宝贵的情报源。通过深入分析可以获得更多安全洞见攻击趋势分析统计每天/每周的攻击IP数量、来源国家分布、常用用户名TOP 10等。这能帮助你了解当前面临的主要威胁态势。例如如果你发现来自某个国家的攻击突然增多可以提醒自己重点关注。0-day漏洞利用尝试高水平的攻击者会尝试利用SSH服务的未公开漏洞。日志中可能会出现异常协议版本、畸形的密钥交换包等记录。虽然我们的脚本主要关注密码失败但可以扩展规则捕获这些异常日志条目并发出更高级别的警报。关联分析将SSH攻击日志与其他服务的攻击日志如Web应用的暴力登录、数据库的爆破尝试进行关联。如果同一个IP在短时间内同时攻击了SSH、MySQL和Wordpress后台那它几乎可以肯定是恶意主机其威胁等级应该被调至最高。5.4 一个实用的进阶技巧使用威胁情报平台除了自己收集还可以利用公开的威胁情报平台Threat Intelligence Platform来丰富你的黑名单。例如有一些开源或商业的IP信誉列表会汇总已知的恶意扫描IP、僵尸网络IP、Tor出口节点等。你可以写一个脚本定期从这些可信的威胁情报源如blocklist.de、abuse.ch等下载IP列表与你自己的列表进行合并去重形成一个更强大的综合黑名单。这相当于站在了巨人的肩膀上极大地扩展了你的防御视野。最后必须再次强调安全底线自动化封禁IP是一把双刃剑。务必确保你的管理通道例如通过物理控制台、带外管理、或者一个绝对可信且未被列入任何黑名单的IP永远畅通。在将任何自动化屏蔽规则应用到生产环境之前一定要在测试环境中充分验证。我曾经因为一个脚本的BUG差点把自己的办公网IP段给封了幸好有备用管理方式。这件事让我深刻理解到安全工具的自动化程度越高对其可靠性和安全性的要求也就越高。这个“SSH攻击IP列表”项目本质上是一个持续迭代、不断学习的过程它让我对服务器面临的安全威胁有了更直观、更深刻的认识。