资讯详情

Python实现多协议主机发现与服务指纹识别:端口扫描实战

📅 2026/10/24 2:47:07 | 华诺云谱 👁 阅读
Python实现多协议主机发现与服务指纹识别:端口扫描实战
做网络资产管理这行主机发现和服务指纹识别是绕不开的两件事。前阵子我基于Python完整实现了一套多协议主机发现与服务指纹识别系统核心功能就是端口扫描检测落地之后在几轮内网环境里试用效果比预期扎实。这篇文章不写照本宣科的设计文档就讲这套系统实际是怎么想的、怎么写的、踩了哪些坑给同样在折腾网络资产梳理和安全评估的同学做个参考。这套系统解决什么问题一句话在拿到授权的前提下快速摸清目标网段里有哪些设备活着这些设备开了哪些端口端口后面跑的是什么服务、什么版本。主机发现负责“找到目标”指纹识别负责“认清目标”两者串起来就是一份基础网络资产清单。适合三类人看一是做网络运维和资产管理的定期要导出设备清单二是做安全防御工作的需要掌握内网暴露面三是对端口扫描原理感兴趣想自己动手写扫描工具的学生或开发者。1. 为什么做这套系统主机发现与指纹识别的现实需求1.1 网络管理的第一步永远是“盘活资产”写代码之前先聊需求。在真实项目里最典型的场景是某天IT要统计公司网段内在线设备数量结果发现Excel台账过期两年了或者安全部门要做暴露面检查想知道哪些服务器把MySQL、Redis暴露到了非预期网段。这种时候你手上最好用的工具往往是现成的Nmap之类但如果需要在项目里批量调度、自动汇总结果、对接后续的资产管理流程现成工具就捉襟见肘了。于是自研一套基于Python的主机发现与指纹识别系统就顺理成章。这里有个核心问题为什么主机发现不是简单的“ping一下”因为现实网络远比教科书复杂。目标主机可能禁ping企业防火墙可能拦截ICMP某些环境的安全策略把echo request挡在外面。只靠一种探测协议漏报率会高得离谱。所以设计原则从一开始就很明确——多协议组合探测互相兜底。ICMP失败换TCPTCP不通换ARPARP在跨网段时失效就回到TCP端口探测最后用存活判定算法把多路结果合并而不是依赖单个协议的一锤子买卖。1.2 方案选型为什么自研而不是直接调现成工具先回答一个大家都会问的问题Nmap已经这么成熟为什么还要自己写我的理由有三条。第一集成与自动化。实际项目里扫描只是上游环节后续还要把结果写数据库、生成资产标签、定期重扫比对变更这些逻辑写进Nmap的脚本体系可以但维护成本高在Python程序里直接调subprocess调外部工具也是一种方案只是解析输出、处理异常、控制并发都不如原生库顺手。第二定制协议序列。某些场景需要自定义探测顺序比如先快速ARP扫本地子网再对跨网段机器做TCP半开扫描靠外部工具拼命令能做但灵活度不够改参数要动一大串配置。第三学习价值。端口扫描检测、TCP状态机、指纹识别这些底层原理光看文档记不住亲手实现一遍印象完全不同。这里我不贬低现成工具反而是建议先用Nmap验证你的指纹库覆盖度再在自研系统里复用这些规则。实际开发时我参考了常见服务指纹库的设计思路把特征按协议类型分组比如HTTP类、SSH类、数据库类、中间件类每个特征包含匹配正则、版本提取规则、辅助识别信息。这套结构在后面的识别模块里会详细展开。依赖方面我只选相对精简的核心不依赖重型框架网络部分基于标准库的socket和concurrent.futures唯一可选的第三方库是scapy用于ARP探测构造包。减少依赖的最大好处是部署方便在对方服务器上pip install之后就能跑不用为环境折腾半天。2. 多协议主机发现不同网络环境下的探测策略2.1 ICMP与TCP探测跨网段扫描的主力主机发现模块我分成4条探测链路ICMP Echo探测、TCP端口探测、ARP探测、UDP辅助探测。先看前两条。ICMP是最直觉的方案就是ping。实际操作时遇到一个很典型的坑在Windows上直接用socket发ICMP原始包死活发不出去原因是Windows对原始套接字限制很严需要系统权限加上额外参数。后来我改成调用系统ping命令解析返回码来判断存活跨平台兼容性一下好了很多。核心逻辑很简单subprocess调用ping设置超时和发送次数返回码为0说明主机在线。但ICMP探测的覆盖率有限很多防火墙默认丢弃ping包却有业务端口对外开放。所以TCP探测是比ICMP更可靠的存活依据。思路是如果在目标主机上发现任何一个端口是开放的基本可以认定这台主机存活。代码里我会挑选一小批高频开放端口做“存活探测集”比如53、80、443、22、3389这类通常对外暴露的端口然后用TCP Connect尝试连接能连通就算命中存活。这个方案还有个好处——数据是现成的端口扫描阶段本来就要做只需把结果复用到存活判定里。这里有个选型细节TCP Connect和TCP半开扫描SYN扫描的区别。TCP Connect是完整三次握手系统socket接口直接可用对用户态程序来说实现最简单缺点是会在目标主机留下连接记录。SYN扫描只发送SYN包、不完成握手速度快且隐蔽但需要root权限构造原始数据包。我在这套系统里默认用TCP Connect因为大部分业务场景不需要隐蔽性稳定性优先只在明确需要低侵入扫描时才切换到基于scapy构造SYN包的半开模式。2.2 ARP发现与UDP辅助内网场景的高效补充在局域网环境里最高效的主机发现方法其实是ARP探测。原理简单说目标IP在同一个二层网络时你发一个“谁是某IP”的ARP请求只要这台机器在线它必须回复“我是某IP我的MAC是什么”。这步在网络栈层面绕不开比TCP和ICMP都可靠。本地子网的主机发现用ARP扫描速度极快准确率基本可以做到很高。实现上推荐用scapy构造ARP请求和以太网广播帧写起来只比文档示例多一点。需要提醒的是ARP探测依赖二层广播只能用于发现同一网段内的设备路由器不会转发ARP广播所以跨网段扫描时ARP结果直接丢弃即可不影响判定。UDP辅助探测用得相对少因为UDP端口开放不代表一定有响应同一端口的应答行为在不同系统间差异太大。我的场景限制在少数已知服务上比如目标网段有DNS服务器就可以对53端口发一个特定DNS查询返回响应说明主机在线而且DNS服务确实开着。DHCP、SNMP也有类似做法。这条链路我只保留了一个可选项默认不开启因为误报率不好控制扫出来一个“主机存活”结果实际是中间设备或网关的应答反而干扰资产准确率。2.3 并发设计线程池控制下的存活判定策略主机发现如果串行执行200台设备的网段要跑好几分钟体验很差。我用concurrent.futures.ThreadPoolExecutor做了并发控制实测下来线程数设在100-200之间效果不错太低的话快不了多少太高的话网络栈丢包严重误报率反而上升。扫描一个/24网段254个IP四种协议联动大概十几秒就能出全量结果这个速度用在日常资产刷新上完全够。存活判定策略是这套系统比较精妙的部分。我定义了一个“可信度得分”模型ARP命中权重最高因为二层应答可信度最高TCP端口命中其次因为说明有真实服务在监听ICMP再次之毕竟禁ping环境太多了。单路命中直接判定存活多路都没有命中但结果不一致时再额外补一轮TCP 80和443的二次确认。实际跑下来这个策略把存活漏报率控制在很低水平代价是少部分防火墙严格的主机可能会被多打一次TCP探测对正常业务几乎无感知。这里也踩了一个经典坑早期版本只做ICMP探测内网里一批打印机和摄像头全部漏报因为设备固件默认禁ping但开放了8080、8000等管理端口。后来把TCP结果关联进存活判定才把漏报追回来。这个案例我印象很深正在做主机发现的同学一定把TCP探测纳入存活机制而不是单独靠ping。3. 服务指纹识别与端口检测从端口数字到服务身份3.1 端口扫描的三级模型常见端口、全端口、服务识别端口扫描的核心问题有两个端口是不是开放开放了跑什么服务。我设计成三级递进。第一级高频端口列表扫描。内置一份常见服务端口清单覆盖22、53、80、443、3306、5432、6379、8080、8443、9200这类主流服务。第一轮扫描只打这批端口速度快适合批量梳理。第二级全端口扫描。如果对某些重点资产要做深度检查就切换到1-65535全端口扫描配合TCP Connect模式一个IP的全量扫描大概需要几十秒到几分钟取决于并发和网络质量。第三级服务识别。只有发现开放端口后才会进入指纹识别环节不会对关闭端口做无意义的探测。从工程上讲端口扫描的无脑connect方式有个注意点不能对每个端口都创建独立socket再销毁那对系统fd压力太大。扫描频率高的时候系统会出现大量TIME_WAIT连接导致后续连接变慢。一个常用的缓解方案是对socket设置SO_LINGER为0主动关闭时直接重置而不是走四次挥手。这个手段对线上服务不友好但用在扫描客户端上是常见做法。另外一个更基础的问题是文件描述符上限如果你在扫描过程中发现报“Too many open files”把进程的ulimit调大就行。3.2 Banner抓取与特征正则指纹识别怎么落地端口一旦确定为开放状态就进入服务指纹识别。最可靠的方法之一是对端口主动发起协议预设的握手请求读取目标的Banner头。比如SSH服务连接后通常主动返回“SSH-2.0-OpenSSH_8.9p1”这类版本信息HTTP服务收到GET请求后响应里的Server头会给出nginx或apache的信息数据库类服务则在连接后返回各自的认证Banner。Banner抓取的本质是“发一句话听对方怎么回”。具体实现时每种协议写一个可发payload连上端口后发送这个payload再接收端口返回的数据。对HTTP这类文本协议payload是构造好的GET请求对SMTP、FTP则直接等待服务器欢迎语。需要特别注意编码问题很多老服务的Banner里夹杂二进制数据要么用errorsignore过滤非法字节要么统一字符集解码后做清洗否则后续正则匹配很容易因为一个非预期字符崩掉。指纹库是系统的核心资产我维护了一个JSON格式的特征库每条记录包含协议类型、特征正则、版本提取正则、归属服务名、备选服务名。识别时按协议分组匹配先判定是否为HTTP再看是否为SSH或数据库协议。匹配顺序有个经验先匹配最具体、最容易误判的协议类型再匹配通用型协议。比如HTTP的Server特征存在时直接进入Web服务识别子流程如果数据包既没有明显Banner也不是标准协议回复就归于“未知服务”同时保留原始Banner文本方便人工归类。3.3 指纹可信度评估识别结果不能百分之百信做过指纹识别的人都知道识别结果天然存在不确定性。同一端口上可能跑着反向代理Banner显示的是nginx后面真实服务是Tomcat有些设备Banner故意伪装或直接不响应。所以我给每条识别结果设计了可信度字段强匹配Banner头部直接出现协议标志加版本号打高分弱匹配只有端口号符合常识、没有可解析Banner打低分无匹配就返回unknown并附原始包内容。最终输出到报告时这份可信度字段能帮下游判断哪些结果需要人工复核。举个例子某台设备开放443端口Banner显示“Server: nginx/1.18.0”指纹库判定为nginx可信度评分很高。后来人工核查发现这是内网一台交换机的Web管理界面只不过底层用了nginx做静态资源服务。这种情况不是系统错了是指纹识别体系本来就只能定位到“应用组件”定位不到“业务系统”。从资产管理角度看识别结果应该和IP归属、网段角色做关联分析才有意义。这也是我后来加入资产标签关联功能的原因单纯一份端口服务清单作用大打折扣。4. 核心代码实现主机发现、端口扫描与指纹模块的工程细节4.1 主机发现模块的实现代码下面给出主机发现模块的核心代码这是系统里复用度最高的部分。用Python标准库实现TCP探测用scapy做ARP扫描import concurrent.futures import ipaddress import socket import subprocess import sys DEFAULT_LIVE_PORTS [80, 443, 22, 53, 3389] PING_EXE ping def tcp_probe(host: str, port: int, timeout: float 1.5): TCP Connect探测返回布尔值表示端口是否开放 try: with socket.create_connection((host, port), timeouttimeout): return True except (socket.timeout, ConnectionRefusedError, OSError): return False def check_live_by_tcp(host: str, timeout: float 1.5) - bool: 用高频开放端口的探测结果综合判断主机是否存活 with concurrent.futures.ThreadPoolExecutor(max_workerslen(DEFAULT_LIVE_PORTS)) as executor: futures {executor.submit(tcp_probe, host, port, timeout): port for port in DEFAULT_LIVE_PORTS} for future in concurrent.futures.as_completed(futures): if future.result(): return True return False def check_live_by_ping(host: str) - bool: 调用系统ping命令判断存活兼容Windows和Linux cmd [PING_EXE, -n, 1, -w, 1500, host] if sys.platform.startswith(win) \ else [PING_EXE, -c, 1, -W, 1, host] ret subprocess.run(cmd, stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) return ret.returncode 0这段代码有两个设计点。第一TCP存活探测并发跑5个高频端口只要一个通就判定存活避免单个端口临时故障引起漏报。第二ping命令用系统自带实现省去处理原始套接字的权限问题。如果你的Python环境具备root权限建议改用自构造ICMP包速度和可控性更好否则系统ping是最稳的选择。ARP扫描的代码基于scapy需要root权限执行from scapy.all import ARP, Ether, srp def arp_scan(subnet: str, timeout: int 2) - list: 扫描本网段所有在线主机返回IP列表 ans, _ srp(Ether(dstff:ff:ff:ff:ff:ff) / ARP(pdstsubnet), timeouttimeout, verbose0) return [recv.psrc for _, recv in ans]注意scapy的srp函数在Windows上依赖底层驱动默认安装后一般能用。我在Linux服务器上实测最稳定建议生产环境优先部署在Linux上。4.2 端口扫描与Banner抓取的代码骨架端口扫描模块的设计是生产消费模式IP队列作为任务池扫描线程从队列取IP顺序探测该IP的端口列表。这里贴一个TCP Connect全端口扫描的简化版from concurrent.futures import ThreadPoolExecutor from queue import Queue import socket def scan_port(host: str, port: int, timeout: float 1.0): try: with socket.create_connection((host, port), timeouttimeout): return port except Exception: return None def scan_host_ports(host: str, ports: list, timeout: float 1.0, workers: int 200): open_ports [] with ThreadPoolExecutor(max_workersworkers) as executor: results executor.map(lambda p: (p, scan_port(host, p, timeout)), ports) for port, is_open in results: if is_open: open_ports.append(port) return open_ports这个版本的性能瓶颈在阻塞IO等待但线程池在等待应答时能让出控制权所以并发效果可观。如果要扫几千台机器建议改成asyncio协程版本后面我找时间单独写一篇协程扫描的笔记。现阶段先用这个版本把流程跑通就好。Banner抓取的代码同样基于socket针对不同协议做分支处理def grab_banner(host: str, port: int, timeout: float 3.0) - str: 通用Banner抓取HTTP端口主动发GET其他端口先读欢迎语 try: s socket.create_connection((host, port), timeouttimeout) try: s.settimeout(timeout) banner if port in (80, 443, 8080, 8443): req (bGET / HTTP/1.1\r\nHost: host.encode() b\r\nUser-Agent: Mozilla/5.0\r\nConnection: close\r\n\r\n) s.sendall(req) banner s.recv(2048).decode(utf-8, ignore).strip() else: banner s.recv(1024).decode(utf-8, ignore).strip() return banner finally: s.close() except Exception: return HTTP分支是实际调试中迭代出来的。很多Web服务连接后不会立刻给数据如果直接蹲着等容易超时但端口确认开放后发一个HTTP请求基本会触发响应。对SSH、FTP这类服务则相反它们通常一连接就主动发Banner。这个“先听后发”与“先发后听”的顺序差异是Banner抓取最容易出偏差的地方必须按协议类型做分支处理不要写死一套。4.3 指纹匹配与结果输出的工程实现指纹库用JSON文件存储匹配逻辑独立成一个模块方便日常增补特征。核心匹配代码大概长这样import re import json def load_fingerprints(path): with open(path, r, encodingutf-8) as f: return json.load(f) def match_fingerprint(banner: str, fingerprints: list): 按特征正则匹配服务指纹返回最佳匹配或None if not banner: return None for fp in fingerprints: try: if re.search(fp[pattern], banner, re.IGNORECASE): version None if version_pattern in fp: m re.search(fp[version_pattern], banner, re.IGNORECASE) version m.group(1) if m else None return { service: fp[service], desc: fp.get(desc, ), version: version, confidence: fp.get(confidence, 60) } except re.error: continue return None匹配顺序需要在调用层做排序把HTTP、SSH、FTP、数据库这些常见协议的特征排前面通用型特征排后面能大幅降低误匹配率。特征库维护有个技巧每次在内网新发现一类服务就把真实Banner存进一个样本池积累到几十条后再统一提炼成正则加入库。我在项目里维护了一个samples目录指纹库不是拍脑袋写的是从真实流量里养出来的。5. 实操中的典型问题与排查经验5.1 漏报与误报最容易翻车的三类场景第一类防火墙过滤导致的ICMP漏报。解决方式是多协议联动TCP端口命中即可确认存活。第二类服务Banner不可靠导致的识别错误。比如某些负载均衡设备对连接的响应完全自定义指纹库没有任何特征。这时候输出unknown比硬猜好因为硬猜会给下游的错误资产信息造成更大麻烦。第三类网关设备的干扰。做跨网段扫描时网关可能会代替真实主机响应某些协议的探测包导致误报。排查方法是做基线比对先扫描一个已知资产清单的测试网段记录哪些IP“多出来”再人工核验这些是否是网关或NAT设备确认后把它们加进静态豁免名单。5.2 超时、并发与性能调优的真实参数我整理了一张调优速查表基于不同规模环境的实测可以直接当初始参数用参数建议初始值调优方向主机级并发线程数100-200网络栈丢包时降低到50端口级并发线程数200-300目标机器性能弱时降低TCP连接超时1.5秒跨专线场景调到3秒ARP扫描超时2秒大型子网提高到4秒重试次数0-1探测不稳定时可设为1Banner超时3秒对慢服务调到5秒以上性能调优要关注的不是单纯“快”而是“快且准”。我试过把线程数提到500结果一个/24网段的扫描时间没下降多少反而丢包率直线上升误报率肉眼可见。原因就是并发过高把所在主机的网络栈和中间设备的会话表压垮了。最稳的思路是先小步调大并发值每加50个线程就扫一次已知网段对照结果一旦出现新增误报就回退到上一档。5.3 结果数据质量的常见坑端口扫描检测系统最怕的不是功能bug而是数据不可信。结果处理上我吃过几次亏总结下来有三点。第一IP地址格式必须统一成字符串存储避免IPv6和IPv4混用导致查询混乱。第二端口状态统计时要区分closed和filteredclosed意味着主机明确拒绝了连接filtered则可能是防火墙拦截没有任何响应这两者性质完全不同。我的代码里用异常类型区分ConnectionRefusedError记为closedsocket.timeout记为filteredOSError记为unknown。第三结果去重不能在端口维度做必须按“IP加服务名加端口”三元组去重否则同一IP上同一服务的多端口版本信息会互相覆盖。6. 合规使用与最后提醒6.1 授权边界和安全底线主机发现和服务指纹识别是双刃剑代码本身没有任何问题关键看用在哪。我必须说得直白一点任何扫描行为都必须提前获得资产所有方的书面授权。没有授权的扫描哪怕只是发一个探测包也是明显越界后果完全超出技术范畴。实操中的建议是第一扫描目标范围必须和授权书严格一致多扫一个不在范围内的IP都是越界。第二扫描强度和频率要克制不要对生产业务造成明显压力。第三所有扫描结果必须加密存储、按需访问扫描日志最少保留半年以上作为事后审计的证据链。我自己的习惯是在系统启动时加入一个交互式授权确认步骤要求操作人输入授权编号并二次确认目标范围否则程序拒绝运行。这个步骤看起来多了一道操作但能有效避免“顺手扫一下”的冲动。这类工具越是被频繁使用越需要把流程控制住。6.2 后续优化方向与个人经验这套系统目前做得还不够完美的地方主要是协程化改造和指纹库自动化升级。协程方案能进一步降低并发开销指纹库自动化则计划通过定期重扫一个受控样本环境自动捕获最新服务的Banner并聚类提炼出新增特征。不过这些都是锦上添花当前TCP Connect加多协议存活判定的组合已经能覆盖绝大部分日常场景。最后分享一点个人体会做网络资产工具准确性永远比功能数量重要。一个能准确识别500种服务但偶尔误导方向的系统不如一个只识别50种服务但每个结果都可信的系统。设计指纹库和判定逻辑时我一直带着这个思路宁可输出unknown也不乱猜。这套系统的设计思路和关键代码已经整理在上面如果你也在做类似项目可以先把你自己的协议特征库养起来再逐步叠加全端口扫描和协程化资产管理的底子会越打越厚。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑