1. 项目概述为什么我们需要一个HTTP/1.*协议扫描器在网络安全运维和业务风控的日常工作中我们经常会遇到一个看似简单却影响深远的问题如何快速、准确地识别出那些仍然在使用老旧、低效甚至存在安全隐患的HTTP/1.*协议的网站或服务尤其是在处理大规模IP黑名单时这个问题变得更加棘手。一个IP被列入黑名单可能是因为它发起了恶意扫描、参与了DDoS攻击或者承载了钓鱼网站。但如果我们想进一步分析这些“坏”IP背后的服务特征比如它们使用的HTTP协议版本手动操作几乎是不可能的。这就是“经常用软件扫描网站 使用HTTP/1.*协议的 黑名单ip”这个项目标题背后的核心需求。它不是一个简单的端口扫描而是一个针对特定应用层协议特征的深度探测工具。其价值在于它能从海量的黑名单IP中自动化地筛选出那些服务端技术栈可能较为陈旧、存在潜在安全漏洞如无法支持HTTP/2的头部压缩、多路复用等安全增强特性或性能低下的目标。对于安全研究人员这可以缩小漏洞挖掘的范围对于运维人员这有助于评估自身对外服务的协议现代化程度和潜在风险。简单来说这个工具能帮你回答“在已知的威胁IP列表中哪些还在跑着老掉牙的HTTP/1.0或HTTP/1.1” 这个答案对于构建更精准的防御策略、进行安全态势分析甚至作为竞争对手技术栈分析的参考都极具意义。2. 核心思路与技术选型实现这样一个扫描器核心思路可以分解为三个步骤获取目标IP列表、与目标建立TCP连接并发送HTTP请求、解析响应以判断协议版本。听起来简单但每个环节都有大量细节需要考虑尤其是在处理大规模、非友好的黑名单IP时。2.1 整体架构设计一个健壮的扫描器不应该是一个“一次性”的脚本。我设计的架构通常包含以下模块输入模块负责加载和管理黑名单IP列表。这些IP可能来自公开的威胁情报源、内部防火墙日志、入侵检测系统IDS的告警等。列表格式可能是每行一个IP也可能是带端口的IP:Port形式。扫描引擎模块这是核心负责并发地对目标IP的特定端口通常是80和443建立连接发送精心构造的探测请求并接收响应。协议分析模块解析扫描引擎返回的原始响应从Server头、响应状态行如HTTP/1.1 200 OK或整个交互过程来判断使用的HTTP协议版本。结果输出模块将判定为使用HTTP/1.*协议的目标以结构化的格式如CSV、JSON保存下来便于后续分析。调度与日志模块管理扫描任务队列、控制并发度、处理超时和异常并记录详细日志用于排错和审计。2.2 关键技术选型与考量为什么用这些技术背后有充分的理由。编程语言Python vs. GoPython首选。其socket、http.client库足以完成基础网络通信而asyncio或第三方库如aiohttp、httpx提供了强大的异步并发能力能极大提升扫描海量IP的效率。生态中还有requests虽然同步用于快速原型验证。对于快速开发、原型验证和复杂逻辑处理Python的灵活性和丰富的库支持是巨大优势。Go如果追求极致的性能和资源效率Go是更佳选择。其原生并发模型goroutine非常适合这种高并发的I/O密集型任务编译成单一二进制文件也便于分发。但在开发速度和生态库的广度上初期可能不如Python便捷。选择建议对于大多数场景从开发效率和生态支持角度我推荐使用Python的asyncioaiohttp方案。它能很好地平衡性能与开发成本。并发模型异步IOAsyncio同步请求一个接一个扫描上万个IP是不可行的耗时太长。我们必须采用并发。多线程/多进程是传统方案但在面对数万甚至数十万级别的连接时线程/进程切换的开销和内存占用会成为瓶颈。异步IO模型在单个线程内通过事件循环处理大量网络连接在连接等待响应I/O阻塞时去处理其他连接的任务非常适合这种“高并发、低计算”的网络扫描场景。Python的asyncio库正是为此而生。探测策略如何“问”出协议版本直接请求法向目标发送一个最简单的HTTP GET请求如GET / HTTP/1.1然后分析响应行。这是最直接的方法。Upgrade试探法在请求头中加入Upgrade: h2c用于HTTP/2明文连接或Upgrade: websocket观察服务器响应。如果服务器支持HTTP/2它可能会返回101 Switching Protocols或直接拒绝Upgrade并继续用HTTP/1.1响应。这可以用来辅助判断服务器是否“仅支持”HTTP/1.*。TLS ALPN应用层协议协商对于HTTPS端口443可以在TLS握手阶段通过ALPN扩展告知服务器客户端支持的协议列表如h2,http/1.1。服务器会选择其中一个作为后续通信协议。这是判断HTTPS服务支持HTTP/2的最标准、最可靠方法。但这需要建立完整的TLS连接开销略大。选择建议对于初步扫描我通常采用“直接请求法”结合“连接超时快速失败”策略。因为它最简单、最快能覆盖大多数HTTP服务。对于需要更高准确性的HTTPS目标可以后续对“直接请求法”筛选出的目标再进行ALPN探测。3. 实操构建从零编写扫描器下面我将以Python asyncioaiohttp为例手把手带你构建这个扫描器的核心部分。我们假设黑名单IP列表是一个名为blacklist_ips.txt的文本文件每行一个IP地址。3.1 环境准备与依赖安装首先确保你的Python版本在3.7以上。然后安装必要的异步HTTP客户端库pip install aiohttpaiohttp不仅提供了高性能的HTTP客户端/服务器还完美集成asyncio是我们实现异步扫描的利器。3.2 核心扫描函数实现我们来编写最核心的异步探测函数。这个函数负责对一个给定的(ip, port)对进行探测。import asyncio import aiohttp from aiohttp import ClientSession, TCPConnector from typing import Tuple, Optional import logging # 配置日志方便调试 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) async def probe_http_version(session: ClientSession, ip: str, port: int 80, timeout: int 10) - Optional[str]: 探测指定IP和端口的HTTP协议版本。 返回 ‘HTTP/1.0‘, ‘HTTP/1.1‘, ‘HTTP/2‘, 或 None (如果探测失败)。 url fhttp://{ip}:{port} # 对于HTTPS需改为 https:// 并使用SSL上下文 headers {User-Agent: Mozilla/5.0 (Compatible; SecurityScanner/1.0)} # 关键设置一个非常短的连接超时和总超时避免在无响应的IP上浪费太多时间 timeout_settings aiohttp.ClientTimeout(totaltimeout, connect5, sock_read5) http_version None try: async with session.get(url, headersheaders, timeouttimeout_settings, allow_redirectsFalse, sslFalse) as response: # 从响应对象中直接获取协议版本信息 # aiohttp 的 response.version 属性会是一个 aiohttp.http.HttpVersion 对象 # 例如 (1, 0), (1, 1), (2, 0) version_tuple response.version if version_tuple (1, 0): http_version HTTP/1.0 elif version_tuple (1, 1): http_version HTTP/1.1 elif version_tuple (2, 0): http_version HTTP/2 else: http_version fHTTP/{version_tuple[0]}.{version_tuple[1]} # 未知版本 logger.debug(fSuccess: {ip}:{port} - {http_version} (Status: {response.status})) return http_version except asyncio.TimeoutError: logger.debug(fTimeout: {ip}:{port}) return None except aiohttp.ClientConnectorError as e: logger.debug(fConnection failed: {ip}:{port} - {e}) return None except aiohttp.ClientError as e: # 捕获其他所有客户端错误如SSL错误、无效响应等 logger.debug(fClient error: {ip}:{port} - {e}) return None except Exception as e: # 兜底捕获任何未预料到的异常 logger.error(fUnexpected error probing {ip}:{port}: {e}, exc_infoTrue) return None代码解读与注意事项异步函数async def定义了这是一个可被await的协程。ClientSession这是aiohttp的核心管理连接池和Cookie。绝对不要在每次请求时都创建新的session而应该在整个扫描过程中复用同一个session这是高性能的关键。超时设置ClientTimeout至关重要。connect控制TCP连接建立的超时sock_read控制从连接建立后到收到响应头的超时。对于黑名单IP很多可能不响应或防火墙丢弃设置较短的超时如5秒能快速跳过它们极大提升整体扫描速度。allow_redirectsFalse我们只关心最初目标服务器的协议版本不跟随重定向。sslFalse因为我们先针对HTTP端口80扫描。如果扫描HTTPS需要设置为True或提供一个SSLContext。对于黑名单IP其证书很可能无效或自签名需要设置sslFalse或创建不验证证书的SSLContext来绕过证书验证错误仅用于扫描探测生产环境切勿禁用验证。异常处理黑名单IP环境复杂连接拒绝、超时、重置、SSL错误是家常便饭。必须全面捕获这些异常避免单个IP的故障导致整个扫描任务崩溃。日志级别使用debug级别记录每个IP的探测详情避免输出过多信息。在正式扫描时可以将日志级别设为WARNING或ERROR只关注错误和最终结果。3.3 主控与并发调度有了单个探测函数我们需要一个“调度员”来管理所有IP的并发探测。async def scan_ip_list(ip_list: list, port: int 80, max_concurrency: int 500): 并发扫描IP列表。 :param ip_list: IP地址列表 :param port: 目标端口 :param max_concurrency: 最大并发连接数控制对目标网络的压力 # 创建TCP连接器限制连接池大小和每主机并发数既是性能优化也是道德考量 connector TCPConnector(limitmax_concurrency, limit_per_host2, ttl_dns_cache300) # 创建ClientSession并传入自定义连接器和超时基准 timeout aiohttp.ClientTimeout(total30) # 总超时作为后备 async with ClientSession(connectorconnector, timeouttimeout) as session: tasks [] results [] for ip in ip_list: # 为每个IP创建一个探测任务 task asyncio.create_task(probe_http_version(session, ip, port)) tasks.append(task) # 等待所有任务完成并收集结果 for task in asyncio.as_completed(tasks): try: http_ver await task # 这里我们只记录使用HTTP/1.*的IP if http_ver and http_ver.startswith(HTTP/1.): # 获取任务对应的IP这里需要一点技巧因为as_completed返回顺序是完成顺序。 # 更稳健的做法是在创建任务时绑定IP。 results.append((ip, http_ver)) # 注意这里的ip变量不对 except Exception as e: logger.error(fTask error: {e}) return results # 改进版将IP与任务绑定 async def scan_ip_list_better(ip_list: list, port: int 80, max_concurrency: int 500): connector TCPConnector(limitmax_concurrency, limit_per_host2) timeout aiohttp.ClientTimeout(total30) async with ClientSession(connectorconnector, timeouttimeout) as session: tasks {} for ip in ip_list: # 将任务与IP绑定在一个字典里 task asyncio.create_task(probe_http_version(session, ip, port)) tasks[task] ip http1_results [] for task in asyncio.as_completed(tasks.keys()): ip tasks[task] try: http_ver await task if http_ver and http_ver.startswith(HTTP/1.): http1_results.append((ip, port, http_ver)) logger.info(fFound HTTP/1.*: {ip}:{port} - {http_ver}) except Exception as e: logger.error(fError processing {ip}:{port}: {e}) return http1_results关键参数解析max_concurrency这是整个扫描器的“油门”。设置得太高如5000可能会对目标网络造成压力甚至触发对方的防御机制也可能耗尽本地资源。设置得太低如50则无法发挥异步IO的优势。我通常从200开始测试根据本地网络带宽和CPU情况调整。对于扫描外部黑名单建议保守一些设置在100-300之间。limit_per_host限制对同一个主机IP的并发连接数。设置为2是合理的避免对单个IP发起洪水攻击。ttl_dns_cacheDNS缓存时间秒。设置为3005分钟可以减少对同一域名的重复解析提升速度。3.4 整合与执行脚本最后我们将所有部分整合到一个主函数中并添加结果输出功能。import sys def load_ip_list(file_path: str) - list: 从文本文件加载IP列表支持简单的注释和空行过滤 ip_list [] with open(file_path, r) as f: for line in f: line line.strip() if line and not line.startswith(#): # 跳过空行和注释 # 简单处理假设每行就是IP或者IP:Port if : in line: ip, _ line.split(:, 1) else: ip line ip_list.append(ip) return ip_list def save_results(results: list, output_file: str): 将结果保存为CSV文件 import csv with open(output_file, w, newline) as f: writer csv.writer(f) writer.writerow([IP Address, Port, HTTP Version]) for ip, port, ver in results: writer.writerow([ip, port, ver]) logger.info(fResults saved to {output_file}) async def main(): if len(sys.argv) 2: print(fUsage: {sys.argv[0]} blacklist_ip_file.txt [port] [concurrency]) sys.exit(1) ip_file sys.argv[1] port int(sys.argv[2]) if len(sys.argv) 2 else 80 concurrency int(sys.argv[3]) if len(sys.argv) 3 else 200 logger.info(fLoading IP list from {ip_file}...) ip_list load_ip_list(ip_file) logger.info(fLoaded {len(ip_list)} IP addresses.) logger.info(fStarting scan on port {port} with concurrency {concurrency}...) results await scan_ip_list_better(ip_list, port, concurrency) logger.info(fScan completed. Found {len(results)} IP(s) using HTTP/1.* protocol.) if results: output_file fhttp1_scan_results_port{port}.csv save_results(results, output_file) # 也可以在控制台简单输出 for ip, port, ver in results[:10]: # 只打印前10条 print(f{ip}:{port} - {ver}) if len(results) 10: print(f... and {len(results) - 10} more.) else: logger.info(No HTTP/1.* services found in the given list.) if __name__ __main__: # 运行异步主函数 asyncio.run(main())使用方式python http1_scanner.py blacklist_ips.txt 80 300这条命令会读取blacklist_ips.txt文件中的IP并发300个任务去探测它们的80端口并将使用HTTP/1.*协议的IP结果保存到http1_scan_results_port80.csv中。4. 高级优化与功能扩展基础的扫描器已经能工作但在真实、复杂的黑名单IP环境中我们还需要考虑更多。4.1 处理HTTPS端口443扫描扫描HTTPS服务需要建立TLS连接。我们需要调整探测函数和会话设置。import ssl from ssl import SSLContext, PROTOCOL_TLS_CLIENT async def probe_https_version(session: ClientSession, ip: str, port: int 443, timeout: int 10) - Optional[str]: 探测HTTPS服务的协议版本优先通过TLS ALPN判断。 url fhttps://{ip}:{port} headers {User-Agent: Mozilla/5.0 (Compatible; SecurityScanner/1.0)} timeout_settings aiohttp.ClientTimeout(totaltimeout, connect5, sock_read5) # 关键创建不验证证书的SSL上下文用于扫描 # WARNING: 这仅在安全扫描探测时使用会接受任何证书包括自签名和过期的。 ssl_context SSLContext(protocolPROTOCOL_TLS_CLIENT) ssl_context.check_hostname False ssl_context.verify_mode ssl.CERT_NONE # 可以设置ALPN协议但aiohttp的ClientSession构造时设置更合适 # 我们主要依赖响应对象的version属性 http_version None try: async with session.get(url, headersheaders, timeouttimeout_settings, allow_redirectsFalse, sslssl_context) as response: version_tuple response.version # ... 版本判断逻辑与HTTP相同 ... if version_tuple (2, 0): http_version HTTP/2 elif version_tuple (1, 1): http_version HTTP/1.1 # ... 其他判断 ... logger.debug(fHTTPS Success: {ip}:{port} - {http_version}) return http_version except Exception as e: logger.debug(fHTTPS probe failed for {ip}:{port}: {e}) return None # 在主函数中需要为HTTPS扫描创建特定的Session async def scan_https_list(ip_list: list, port: int 443, max_concurrency: int 200): ssl_context SSLContext(protocolPROTOCOL_TLS_CLIENT) ssl_context.check_hostname False ssl_context.verify_mode ssl.CERT_NONE connector TCPConnector(limitmax_concurrency, limit_per_host2, sslssl_context) timeout aiohttp.ClientTimeout(total30) async with ClientSession(connectorconnector, timeouttimeout) as session: # ... 剩下的任务创建和结果收集逻辑与scan_ip_list_better类似 ... # 但调用的是 probe_https_version 函数 pass重要安全提示ssl_context.verify_mode ssl.CERT_NONE会禁用所有证书验证使你的连接面临中间人攻击风险。这行代码仅适用于你完全信任的网络环境下的自动化扫描工具并且你明确知道自己在做什么。绝对不要在任何处理敏感数据如登录凭证、个人信息的程序中使用此设置。对于公开互联网扫描这是无奈但常见的做法因为黑名单服务器很可能使用无效证书。4.2 智能端口探测与协议识别一个IP可能开放了多个端口80, 8080, 8000, 443, 8443等。我们可以扩展扫描器使其能够自动探测常见HTTP/HTTPS端口。COMMON_HTTP_PORTS [80, 8080, 8000, 8888, 3000] COMMON_HTTPS_PORTS [443, 8443, 9443] async def probe_ip_all_ports(session_http: ClientSession, session_https: ClientSession, ip: str): 探测一个IP的所有常见端口 results [] # 探测HTTP端口 for port in COMMON_HTTP_PORTS: ver await probe_http_version(session_http, ip, port) if ver: results.append((ip, port, ver, HTTP)) break # 找到一个就跳出取决于你是否想找所有开放端口 # 探测HTTPS端口 for port in COMMON_HTTPS_PORTS: ver await probe_https_version(session_https, ip, port) if ver: results.append((ip, port, ver, HTTPS)) break return results这样扫描器就能更全面地发现目标IP上运行的Web服务。4.3 性能优化与资源管理当IP列表非常大数十万时即使有并发控制一次性创建所有任务也可能消耗大量内存。我们可以使用生产者-消费者模型或信号量Semaphore来控制同时进行的任务数量。import asyncio async def worker(semaphore: asyncio.Semaphore, session: ClientSession, ip: str, port: int, results_queue: asyncio.Queue): 一个工作协程获取信号量后执行探测 async with semaphore: # 控制并发 worker 数量 http_ver await probe_http_version(session, ip, port) if http_ver and http_ver.startswith(HTTP/1.): await results_queue.put((ip, port, http_ver)) async def bounded_scan(ip_list: list, port: int 80, max_workers: int 200): 使用信号量限制并发工作协程数量的扫描 connector TCPConnector(limit_per_host2) timeout aiohttp.ClientTimeout(total30) async with ClientSession(connectorconnector, timeouttimeout) as session: semaphore asyncio.Semaphore(max_workers) results_queue asyncio.Queue() # 创建 worker 任务 tasks [] for ip in ip_list: task asyncio.create_task(worker(semaphore, session, ip, port, results_queue)) tasks.append(task) # 等待所有 worker 完成 await asyncio.gather(*tasks, return_exceptionsTrue) # 从队列中收集结果 results [] while not results_queue.empty(): results.append(await results_queue.get()) return results这种方式能更精细地控制并发度和资源使用适合超大规模扫描。5. 常见问题、排查技巧与伦理考量在实际运行中你肯定会遇到各种各样的问题。下面是我踩过坑后总结的一些经验。5.1 典型问题与解决方案速查表问题现象可能原因排查与解决思路连接超时TimeoutError目标IP防火墙丢弃包、网络路由问题、目标主机已关机。这是最普遍的情况。调低connect超时时间如2秒快速失败。将这些IP标记为“无响应”无需重试。连接被拒绝ConnectionRefusedError目标端口未开放任何服务。正常现象记录即可。说明该IP在探测端口上没有服务。SSL证书验证错误目标使用自签名、过期或无效证书。对于扫描器创建不验证证书的SSLContext如前文所示。务必清楚安全风险此设置仅用于探测。远程主机强制关闭连接ConnectionResetError目标主机在连接建立后立即发送RST包可能是主动拒绝或负载均衡策略。增加sock_read超时或捕获该异常后直接标记为失败。有些安全设备会以此方式回应探测。响应内容截断或畸形目标返回的不是合法的HTTP响应可能是其他协议如SSH、数据库运行在80/443端口。在probe_http_version函数中除了检查response.version还可以检查响应状态码是否在合理范围如100-599。如果无法解析则标记为“非HTTP”或“协议未知”。本地端口耗尽并发数过高短时间内建立了大量连接TIME_WAIT状态端口过多。降低max_concurrency。使用TCPConnector并设置limit和limit_per_host。操作系统层面可以调整net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle谨慎操作。DNS解析缓慢或失败黑名单IP中可能混杂着域名或DNS服务器响应慢。1. 在加载IP列表时尽量只使用IP地址避免域名。2. 使用本地HOSTS文件或设置更快的DNS服务器。3. 在TCPConnector中设置ttl_dns_cache缓存DNS结果。误判HTTP/2有些服务器虽然支持HTTP/2但可能对某些请求如无ALPN的明文连接降级回HTTP/1.1。对于HTTPS最准确的方法是检查TLS握手期间的ALPN。可以使用更低层的库如tls-client或专门工具如h2scan来确认。我们的扫描器结果可视为“初步筛选”。5.2 实操心得与避坑指南速率限制与道德无节制地高并发扫描会对目标网络造成压力可能被视为攻击行为导致你的IP被对方封禁。务必设置合理的并发数如每秒100-200个新连接并考虑在扫描之间添加随机延迟asyncio.sleep(random.uniform(0.1, 0.5))。最好在非业务高峰时段进行扫描。结果去重与验证黑名单IP列表可能存在重复。扫描前对IP进行去重。对于筛选出的HTTP/1.* IP可以二次验证比如用curl -I或浏览器手动访问一下确认服务确实存在且协议判断准确。日志是生命线一定要配置详细的日志至少记录每个IP的扫描开始、结束、成功、失败及原因。当扫描数万IP时没有日志你根本无法定位问题。使用logging模块并合理设置级别调试时用DEBUG运行时用INFO。处理“脏”数据黑名单IP来源复杂可能包含CIDR格式192.168.1.0/24、带端口的ip:port、甚至错误格式。在加载数据阶段就要进行清洗和规范化确保输入是纯净的IP地址列表。异步编程陷阱确保你的所有I/O操作网络请求、文件写入都是异步的或者放在单独的线程池中执行避免阻塞事件循环。例如批量写入结果文件时可以考虑先收集在内存列表扫描结束后一次性写入或使用aiofiles库进行异步文件操作。资源清理确保ClientSession和TCPConnector在async with块中正确关闭以释放连接池。避免在异常情况下资源泄露。5.3 扩展思路让扫描器更有价值基础的协议扫描只是第一步你可以基于此进行丰富服务指纹识别在获取HTTP响应后可以解析Server、X-Powered-By等头部识别Web服务器Nginx/Apache/IIS和后端语言PHP/Java/Python。标题与状态码收集记录网站的Title和HTTP状态码用于快速分类如403禁止访问、500服务器错误等。与漏洞库关联将识别出的老旧协议版本如HTTP/1.0和服务器软件版本与公开漏洞库如CVE关联评估潜在风险等级。可视化与报告将扫描结果生成图表展示HTTP协议版本分布、常见服务器类型等形成分析报告。构建这样一个扫描器的过程不仅是编写代码更是一次对网络协议、并发编程、异常处理和工程规范的深入实践。它最终产出的不仅仅是一个IP列表更是你对网络空间一片特定“区域”技术面貌的洞察力。