
1. 项目概述为什么我们需要一个HTTP/1.*协议扫描器如果你负责过线上服务的运维或者安全监控大概率遇到过这种情况服务器监控告警突然响起日志里充斥着大量来自陌生IP的请求它们行为怪异要么是高频的目录扫描要么是尝试注入攻击用的还是老旧的HTTP/1.0或HTTP/1.1协议。手动去查效率低下用现成的WAFWeb应用防火墙或云安全产品又可能因为规则滞后或成本问题无法第一时间精准拦截。这个项目要解决的就是这样一个痛点*自动化、持续性地扫描访问我们网站的IP识别出那些仍在使用HTTP/1.协议且行为可疑的“坏家伙”并将它们动态加入黑名单。这听起来像是一个简单的日志分析脚本但深入下去你会发现它涉及协议识别、行为分析、实时决策和系统集成等多个层面。仅仅因为使用HTTP/1.*就封禁显然不合理毕竟很多正常的爬虫、老旧客户端也可能使用。核心在于“经常用软件扫描网站”这个行为模式。我们需要的是一个智能的“观察员”它能从海量正常流量中精准地揪出那些带着扫描器特征、使用低效或易于攻击的旧协议、且持续对我们“感兴趣”的IP地址。这个项目的价值在于将被动防御转为主动预警在攻击者真正发动有效攻击前就提前限制其访问能力为我们的服务增加一道自建的、可高度定制的安全缓冲带。2. 核心设计思路从协议特征到行为判定的逻辑链条一个健壮的黑名单IP识别系统不能只靠单一维度做判断。我的设计思路是构建一个多层次的过滤与分析管道像筛子一样层层过滤流量最终留下高风险的IP。整个系统的逻辑链条可以拆解为以下四个核心环节。2.1 协议层过滤为什么紧盯HTTP/1.*首先我们把使用HTTP/1.*协议的请求单独拎出来。这并非歧视旧协议而是基于安全与实践的考量。HTTP/2和HTTP/3在协议层引入了多路复用、头部压缩、强制加密等特性不仅提升了性能也增加了攻击者构造恶意请求的难度。相比之下HTTP/1.1特别是HTTP/1.0协议简单更容易被自动化扫描工具利用。注意这里绝对不是说HTTP/1.1不安全而是说攻击者使用的自动化扫描工具为了追求通用性和低开销大量沿用简单、兼容性最好的HTTP/1.1协议。这是一个非常显著的特征信号。在Nginx或Apache日志中我们可以通过检查$server_protocol变量来区分协议。我们的系统第一步就是实时解析访问日志将HTTP/1.0和HTTP/1.1的请求分离出来作为后续分析的“嫌疑池”。这一步可以过滤掉50%以上的正常流量如今主流浏览器和APP对重要站点都会优先使用HTTP/2极大减轻后续分析的压力。2.2 行为模式分析定义“扫描”与“恶意”这是项目的核心大脑。仅仅使用旧协议不足以定罪我们必须分析其行为模式。一个恶意的扫描器通常有以下几个特征高频访问在短时间内对服务器发起远超正常用户的请求数。路径枚举请求的URL具有规律性例如顺序访问/admin,/wp-login.php,/phpmyadmin/,/config.json等常见的管理后台、配置文件或漏洞路径。方法异常大量使用HEAD、OPTIONS方法探测或对同一路径频繁使用POST尝试注入。低效遍历User-Agent可能是空、默认值如python-requests/2.28.1或已知的扫描器标识如Nmap Scripting Engine。错误集中其请求大量返回404页面未找到、403禁止访问或400错误请求状态码。我们需要为这些特征设定可量化的阈值和规则。例如规则A频率规则单个IP在60秒内发起超过100次HTTP/1.*请求。规则B路径规则单个IP在10分钟内访问超过20个不同的、包含常见漏洞路径关键词如admin,login,config,backup的URL。规则C错误率规则单个IP的请求中4xx和5xx状态码的比例超过70%。当一个IP触发了任意一条或多条规则时它就会被标记为“疑似恶意扫描IP”。2.3 情报关联与误判规避为了避免误封我们需要引入白名单和灰度观察机制。白名单已知的搜索引擎爬虫IP段如Google、Bing、合作方API网关IP、公司内部网络IP等应直接放行不进入分析流程。灰度观察对于首次触发规则的IP不立即拉黑而是将其放入“观察列表”并降低其触发规则的阈值。如果在后续一段时间内如24小时其行为恢复正常则移出列表如果持续作恶则坐实罪名进入黑名单。此外可以集成公开的威胁情报源如AbuseIPDB的API查询IP的历史信誉。如果该IP已在多个站点被报告为恶意那么可以更果断地处理。2.4 黑名单的动态应用识别出黑名单IP后关键在于如何高效地应用它。有几种主流方式防火墙层面推荐使用iptablesLinux或firewalld动态添加规则直接丢弃来自该IP的所有数据包。效率最高对Web服务器零负担。Web服务器层面在Nginx的配置中通过deny指令或在Lua脚本中动态拒绝。这种方式更灵活可以记录详细的拒绝日志但会消耗一定的Web服务器资源。应用层面在业务代码中拦截。不推荐因为压力会传导到应用服务器且可能绕过。本项目的设计是联动防火墙。系统生成黑名单IP列表后通过脚本自动调用iptables命令添加封锁规则并设置一个过期时间例如7天。7天后自动清理规则避免名单无限膨胀。如果该IP在此期间再次被捕获则刷新过期时间。3. 技术实现与核心组件拆解下面我将以一个基于Linux如Rocky Linux、Debian环境使用Nginx作为Web服务器Python作为分析脚本的典型方案为例拆解具体实现。你可以根据实际情况替换组件例如用Go重写分析器以获得更高性能或用Apache替代Nginx。3.1 环境准备与数据源配置Web服务器日志格式化这是数据分析的基石。我们必须确保Nginx能记录下分析所需的关键信息。修改Nginx配置文件/etc/nginx/nginx.conf或站点配置文件使用一个自定义的日志格式http { log_format security $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $server_protocol $request_time $http_host $request_length; access_log /var/log/nginx/security_access.log security; }这个格式包含了IP($remote_addr)、协议($server_protocol)、请求方法路径($request)、状态码($status)、User-Agent等关键字段。之后我们的分析脚本就实时读取这个security_access.log文件。Python分析脚本依赖创建一个Python虚拟环境并安装必要依赖python3 -m venv scanner-env source scanner-env/bin/activate pip install pyinotify # 用于实时监控日志文件变化 pip install requests # 用于查询威胁情报API3.2 核心分析脚本架构脚本的核心是一个持续运行的守护进程它主要做三件事实时读日志、分析判断、执行封禁。1. 实时日志尾随与解析我们使用pyinotify监控日志文件的新增内容避免反复读取整个文件效率极高。import pyinotify import re # 定义日志行解析的正则表达式匹配上面定义的security格式 LOG_PATTERN re.compile( r(?Pip\d\.\d\.\d\.\d)\s-\s(?Puser.*?)\s\[(?Ptime.*?)\]\s r\(?Prequest.*?)\\s(?Pstatus\d)\s(?Pbytes\d)\s r\(?Preferer.*?)\\s\(?Pua.*?)\\s r\(?PprotocolHTTP\/1\.[01])\\s(?Prt.*?)\s # 关键只匹配HTTP/1.* r\(?Phost.*?)\\s\(?Plength.*?)\ ) class LogEventHandler(pyinotify.ProcessEvent): def process_IN_MODIFY(self, event): # 当文件被修改时读取新增行 with open(event.pathname, r) as f: f.seek(self.last_position) # 跳到上次读取的位置 new_lines f.readlines() self.last_position f.tell() for line in new_lines: self.analyze_log_line(line.strip()) def analyze_log_line(self, line): match LOG_PATTERN.match(line) if not match: return # 不匹配我们格式的行跳过 data match.groupdict() # 只处理HTTP/1.*协议请求 if data[protocol].startswith(HTTP/1.): ip data[ip] request data[request] status data[status] # ... 将这条记录存入临时数据结构如字典或队列供后续分析 self.update_ip_stats(ip, request, status)2. IP行为统计与规则判断我们需要在内存中维护一个数据结构来临时存储每个IP的短期行为。通常使用字典键为IP值为一个包含计数器和时间戳的统计对象。from collections import defaultdict from datetime import datetime, timedelta class IPStats: def __init__(self): self.request_count 0 self.error_count 0 self.paths set() # 记录访问的不同路径 self.first_seen datetime.now() self.last_seen datetime.now() def update(self, request_path, status_code): self.request_count 1 if status_code.startswith(4) or status_code.startswith(5): self.error_count 1 self.paths.add(request_path.split(?)[0]) # 去掉查询参数只保留路径 self.last_seen datetime.now() # 在事件处理器中 class LogEventHandler(pyinotify.ProcessEvent): def __init__(self): self.ip_stats defaultdict(IPStats) self.whitelist {192.168.1.0/24, 10.0.0.1} # 示例白名单 self.observation_list {} # 观察列表{ip: 加入时间} self.blacklist set() # 内存中的黑名单持久化到文件 def update_ip_stats(self, ip, request, status): if self.is_whitelisted(ip): return stats self.ip_stats[ip] request_method_path request.split( )[1] if in request else request stats.update(request_method_path, status) # 每更新一次就检查一次规则 if self.check_malicious_rules(ip, stats): self.handle_malicious_ip(ip) def check_malicious_rules(self, ip, stats): now datetime.now() time_window now - timedelta(minutes1) # 规则1: 1分钟内请求超过80次 if stats.request_count 80: return True # 规则2: 错误率超过60% if stats.request_count 10 and (stats.error_count / stats.request_count) 0.6: return True # 规则3: 访问的敏感路径数超过15个 (需要预定义敏感路径关键词列表) sensitive_keywords [admin, login, config, backup, wp-, phpmyadmin, env, .git] sensitive_path_count sum(1 for path in stats.paths if any(kw in path for kw in sensitive_keywords)) if sensitive_path_count 15: return True return False3. 执行封禁与持久化当IP被判定为恶意后我们调用系统命令将其加入防火墙黑名单并记录到文件。import subprocess import json from pathlib import Path BLACKLIST_FILE Path(/etc/nginx/blacklist.conf) IPTABLES_CHAIN INPUT # 根据你的iptables规则调整 def block_ip_with_iptables(ip_address): 使用iptables封锁IP try: # 检查是否已存在规则 check_cmd [sudo, iptables, -C, IPTABLES_CHAIN, -s, ip_address, -j, DROP] result subprocess.run(check_cmd, capture_outputTrue) if result.returncode 0: print(fIP {ip_address} 已在黑名单中跳过。) return # 添加封锁规则 block_cmd [sudo, iptables, -A, IPTABLES_CHAIN, -s, ip_address, -j, DROP] subprocess.run(block_cmd, checkTrue) print(f已成功将IP {ip_address} 加入iptables黑名单。) # 可选添加一条记录日志的规则 # log_cmd [sudo, iptables, -A, INPUT, -s, ip_address, -j, LOG, --log-prefix, BLACKLISTED: ] # subprocess.run(log_cmd) except subprocess.CalledProcessError as e: print(f封锁IP {ip_address} 失败: {e}) def add_to_nginx_blacklist(ip_address): 将IP加入Nginx黑名单配置文件作为备用或记录 deny_directive fdeny {ip_address};\n try: with open(BLACKLIST_FILE, a) as f: f.write(deny_directive) # 重载Nginx配置 subprocess.run([sudo, nginx, -s, reload], checkFalse) print(f已将IP {ip_address} 写入Nginx黑名单文件。) except IOError as e: print(f写入Nginx黑名单文件失败: {e}) def handle_malicious_ip(self, ip): if ip in self.blacklist: return print(f[{datetime.now()}] 检测到恶意扫描IP: {ip}) # 1. 加入内存黑名单 self.blacklist.add(ip) # 2. 执行封禁 block_ip_with_iptables(ip) add_to_nginx_blacklist(ip) # 可选 # 3. 持久化到本地JSON文件用于服务重启后恢复 self.save_blacklist_to_disk() # 4. 可选发送告警通知如邮件、Slack、钉钉等 self.send_alert_notification(ip)3.3 系统优化与高可用考量一个生产可用的系统还需要考虑更多细节性能优化分析频率不要每条日志都触发全量规则计算。可以设置一个时间切片如每5秒或每收集到100条HTTP/1.*日志对ip_stats字典做一次批量规则判断。数据结构对于高流量网站ip_stats字典可能巨大。需要定期清理长时间不活跃的IP记录例如last_seen超过1小时。可以使用expiringdict这类库或自己实现一个带TTL的缓存。日志轮转处理Nginx日志会轮转rotate。pyinotify需要能处理日志文件被移动和新建的情况。通常需要监控日志目录而不仅仅是单个文件。可靠性提升黑名单持久化与恢复将self.blacklist定期如每分钟保存到磁盘文件如JSON格式。当分析脚本重启时首先从文件加载黑名单并遍历所有IP确保iptables规则已存在防止遗漏。规则热加载将规则如阈值、敏感路径关键词放在外部配置文件如YAML中支持在不重启脚本的情况下更新规则。进程守护使用systemd服务单元文件将Python脚本托管为守护进程确保崩溃后自动重启。误封规避强化观察期机制实现一个完善的观察期。首次触犯规则的IP进入observation_list并标记为“嫌疑”。在观察期内如30分钟对其使用更严格的阈值例如原规则请求数阈值减半。如果观察期内持续触发则转正为黑名单如果行为恢复正常则在观察期结束后移出列表。威胁情报查询在判定为恶意前调用AbuseIPDB等免费API查询该IP的信誉分。如果信誉极差则跳过观察期直接封禁如果信誉良好则可能放宽条件或加入白名单观察。4. 部署、调试与日常运维指南将代码跑起来只是第一步让它稳定、准确地运行才是关键。4.1 分步部署流程环境检查确保服务器是Linux系统已安装Python3、pip、nginx/apahe。确保你有sudo权限来执行iptables命令。配置Nginx按前文修改Nginx配置并重载(nginx -s reload)。检查/var/log/nginx/security_access.log是否开始生成新格式的日志。准备脚本将完整的Python脚本包含事件处理、规则判断、封禁逻辑保存到服务器例如/opt/ip_scanner/scanner.py。记得修改其中的白名单、规则阈值、文件路径等配置项。设置权限为了让脚本能执行sudo iptables最安全的方式不是给脚本sudo免密而是配置/etc/sudoers文件允许运行此脚本的用户无需密码执行特定的iptables命令。# 在/etc/sudoers.d/目录下创建一个文件如ip_scanner # 内容为your_username ALL(ALL) NOPASSWD: /sbin/iptables -A INPUT -s * -j DROP, /sbin/iptables -C INPUT -s * -j DROP创建Systemd服务创建文件/etc/systemd/system/ip-scanner.service。[Unit] DescriptionHTTP/1.* Malicious IP Scanner Afternetwork.target nginx.service [Service] Typesimple Useryour_username WorkingDirectory/opt/ip_scanner ExecStart/usr/bin/python3 /opt/ip_scanner/scanner.py Restarton-failure RestartSec5s [Install] WantedBymulti-user.target启动与测试sudo systemctl daemon-reload sudo systemctl start ip-scanner sudo systemctl enable ip-scanner # 开机自启 sudo tail -f /var/log/nginx/security_access.log # 观察日志 # 使用另一台机器用curl或扫描器模拟恶意请求观察脚本是否告警并封禁。4.2 常见问题与排查技巧在实际运行中你肯定会遇到各种问题。下面是我踩过坑后总结的排查清单问题1脚本运行后没有任何输出也没封禁IP。检查日志源确认Nginx的security_access.log路径与脚本中监控的路径完全一致。用tail -f命令手动查看该文件是否有新内容写入。检查协议过滤确认你的测试请求使用的是HTTP/1.1。有些测试工具默认可能用HTTP/2。可以用curl --http1.1来强制指定。检查权限运行脚本的用户是否有权限读取Nginx日志文件通常需要将用户加入adm或nginx组。检查Python依赖pyinotify是否安装成功在脚本开头加import sys; print(sys.path)打印路径确认。问题2误封了正常用户或搜索引擎。审查白名单立即将误封IP加入白名单。检查你的白名单是否涵盖了所有CDN节点如Cloudflare、合作伙伴IP段、公司办公网IP。调整规则阈值你的规则可能太严格了。提高频率阈值如从1分钟80次调到150次或降低错误率阈值如从60%调到80%。规则调整是一个持续调优的过程。引入观察期确保观察期机制正常工作给首次触犯规则的IP一个“改过自新”的机会。分析误封请求从日志中筛选出该IP的所有请求分析其User-Agent、访问路径模式看是否能总结出新的特征用于优化规则或白名单。问题3iptables规则过多影响性能或管理混乱。使用独立的Chain不要直接往INPUT链加规则。创建一个自定义链BLACKLIST。sudo iptables -N BLACKLIST sudo iptables -A INPUT -j BLACKLIST然后脚本的封禁命令改为sudo iptables -A BLACKLIST -s IP -j DROP。这样所有黑名单规则都集中在一个链里管理、查看、清空都方便。定期清理旧规则在脚本中维护黑名单时为每个IP记录封禁时间。可以另写一个定时任务Cron Job每天检查并删除封禁时间超过7天或你设定的期限的iptables规则防止规则集无限膨胀。考虑使用ipset对于海量IP封禁iptables逐条规则效率会下降。可以使用ipset它是一个高效的IP集合管理工具。封禁时将IP加入一个ipset集合然后只需一条iptables规则匹配这个集合即可。这对性能提升巨大。问题4脚本内存占用越来越高。内存泄漏检查ip_stats字典只增不减是主因。必须实现清理机制。在check_malicious_rules函数中或单独开一个定时线程定期如每小时遍历ip_stats删除last_seen超过一定时间如2小时的条目。使用更高效的数据结构对于路径集合stats.paths如果只关心数量可以只存储一个计数器每遇到一个新路径就加1。但如果需要做敏感路径匹配可能仍需存储集合。可以考虑使用Bloom Filter等概率数据结构来节省空间但这会带来一定的误判率。问题5如何验证封禁是否生效从被封禁的IP或模拟该IP尝试访问网站应收到Connection timed out或直接被拒绝。如果网站还能访问检查iptables规则是否添加成功sudo iptables -L -n -v | grep IP。服务器是否开启了其他防火墙如firewalld、云服务商的安全组需要同步配置。Nginx层面的deny指令是否生效如果用了的话。4.3 监控与告警集成一个成熟的系统离不开监控。你需要知道它是否在正常工作以及拦截了多少攻击。脚本状态监控通过Systemd可以方便地查看服务状态sudo systemctl status ip-scanner。也可以配置systemd的日志转发用journalctl -u ip-scanner -f来实时查看脚本输出。关键指标日志让脚本在每次封禁IP时不仅打印到控制台也结构化地记录到一个单独的日志文件或发送到监控系统如Prometheus。记录的信息应包括时间戳、IP地址、触发的规则、该IP的总请求数、错误率、访问的敏感路径示例。外部告警集成邮件、Slack、钉钉、企业微信等。当有IP被加入黑名单时发送一条告警消息包含上述关键指标方便安全团队跟进分析。定期报告可以写一个简单的统计脚本每天汇总封禁IP数量、主要攻击类型根据触发规则判断、来源地域通过IP查询等生成报告。这个项目从一个小小的需求点出发逐步构建了一个具备实时分析、智能判断、动态防御能力的小型安全系统。它不替代专业的WAF但作为一道自建、低成本、高定制化的防线在应对自动化扫描和低频攻击时非常有效。最重要的是通过亲手搭建它你能深刻理解网络流量分析、安全攻防的基本逻辑以及如何将运维、开发和安全知识融会贯通。